C++11模版元编程

1.概述

  模版元编程(template metaprogram)是C++中最复杂也是威力最强大的编程范式,它是一种可以创建和操纵程序的程序。模版元编程完全不同于普通的运行期程序,它很独特,因为模版元程序的执行完全是在编译期,并且模版元程序操纵的数据不能是运行时变量,只能是编译期常量,不可修改,另外它用到的语法元素也是相当有限,不能使用运行期的一些语法,比如if-else,for等语句都不能用。因此,模版元编程需要很多技巧,常常需要类型重定义、枚举常量、继承、模板偏特化等方法来配合,因此编写模版元编程比较复杂也比较困难。

为了帮助您更好地入门并深入掌握C++,我们精心准备了一系列丰富的学习资源包,包括但不限于基础语法教程、实战项目案例、核心概念解析以及进阶技巧指导等。

您只扫码上方二维码,即可免费获取这份专属的学习礼包。我们的教程覆盖了C++语言的各个方面,旨在让您在理论学习与实践操作中不断进步,提升编程技能。

同时,我们也鼓励您在学习过程中遇到任何问题时积极提问,我们会尽全力提供解答和帮助。期待您在C++编程的道路上越走越远,早日成为一位优秀的C++开发

  现在C++11新增了一些模版元相关的特性,不仅可以让我们编写模版元程序变得更容易,还进一步增强了泛型编程的能力,比如type_traits让我们不必再重复发明轮子了,给我们提供了大量便利的元函数,还提供了可变模板参数和tuple,让模版元编程“如虎添翼”。本文将向读者展示C++11中模版元编程常用的技巧和具体应用。

2.模版元基本概念

  模版元程序由元数据和元函数组成,元数据就是元编程可以操作的数据,即C++编译器在编译期可以操作的数据。元数据不是运行期变量,只能是编译期常量,不能修改,常见的元数据有enum枚举常量、静态常量、基本类型和自定义类型等。

  元函数是模板元编程中用于操作处理元数据的“构件”,可以在编译期被“调用”,因为它的功能和形式和运行时的函数类似,而被称为元函数,它是元编程中最重要的构件。元函数实际上表现为C++的一个类、模板类或模板函数,它的通常形式如下:

template<int N, int M>
struct meta_func
{
    static const int value = N+M;
}

  调用元函数获取value值:cout<<meta_func<1, 2>::value<<endl;

  meta_func的执行过程是在编译期完成的,实际执行程序时,是没有计算动作而是直接使用编译期的计算结果的。元函数只处理元数据,元数据是编译期常量和类型,所以下面的代码是编译不过的:

int i = 1, j = 2;
meta_func<i, j>::value; //错误,元函数无法处理运行时普通数据

  模板元编程产生的源程序是在编译期执行的程序,因此它首先要遵循C++和模板的语法,但是它操作的对象不是运行时普通的变量,因此不能使用运行时的C++关键字(如if、else、for),可用的语法元素相当有限,最常用的是:

  • enum、static const,用来定义编译期的整数常量;
  • typedef/using,用于定义元数据;
  • T、Args...,声明元数据类型;
  • template,主要用于定义元函数;
  • "::",域运算符,用于解析类型作用域获取计算结果(元数据)。

如果模板元编程中需要if-else、for等逻辑时该怎么办呢?

模板元中的if-else可以通过type_traits来实现,它不仅仅可以在编译期做判断,还可以做计算、查询、转换和选择。

模板元中的for等逻辑可以通过递归、重载、和模板特化(偏特化)等方法实现。

下面来看看C++11提供的模版元基础库type_traits。

3.type_traits

  type_traits是C++11提供的模板元基础库,通过type_traits可以实现在编译期计算、查询、判断、转换和选择,提供了模板元编程需要的一些常用元函数。下面来看看一些基本的type_traits的基本用法。

  最简单的一个type_traits是定义编译期常量的元函数integral_constant,它的定义如下:

template< class T, T v >
struct integral_constant;

  借助这个简单的trait,我们可以很方便地定义编译期常量,比如定义一个值为1的int常量可以这样定义:

using one_type = std::integral_constant<int, 1>;

或者

template<class T>
struct one_type : std::integral_constant<int, 1>{};

 获取常量则通过one_type::value来获取,这种定义编译期常量的方式相比C++98/03要简单,在C++98/03中定义编译期常量一般是这样定义的:

template<class T>
struct one_type
{
    enum{value = 1};
};

template<class T>
struct one_type
{
    static const int value = 1;
};

  可以看到,通过C++11的type_traits提供的一个简单的integral_constant就可以很方便的定义编译期常量,而无需再去通过定义enum和static const变量方式去定义编译期常量了,这也为定义编译期常量提供了另外一种方法。C++11的type_traits已经提供了编译期的true和false,是通过integral_constant来定义的:

typedef  integral_constant<bool, true> true_type;
typedef  integral_constant<bool, false> false_type;

  除了这些基本的元函数之外,type_traits还提供了丰富的元函数,比如用于编译期判断的元函数:

  这只是列举一小部分的type_traits元函数,type_traits提供了上百个方便的元函数,读者可以参考http://en.cppreference.com/w/cpp/header/type_traits,这些基本的元函数用法比较简单:

#include <iostream>
#include <type_traits>

int main() {
  std::cout << "int: " << std::is_const<int>::value << std::endl;
  std::cout << "const int: " << std::is_const<const int>::value << std::endl;

  //判断类型是否相同
  std::cout<< std::is_same<int, int>::value<<"\n";// true
  std::cout<< std::is_same<int, unsignedint>::value<<"\n";// false

  //添加、移除const
  cout << std::is_same<const int, add_const<int>::type>::value << endl;
  cout << std::is_same<int, remove_const<const int>::type>::value << endl;

  //添加引用
  cout << std::is_same<int&, add_lvalue_reference<int>::type>::value << endl;
  cout << std::is_same<int&&, add_rvalue_reference<int>::type>::value << endl;

  //取公共类型
  typedef std::common_type<unsigned char, short, int>::type NumericType;
  cout << std::is_same<int, NumericType>::value << endl;

  return 0;
}

   type_traits还提供了编译期选择traits:std::conditional,它在编译期根据一个判断式选择两个类型中的一个,和条件表达式的语义类似,类似于一个三元表达式。它的原型是:

template< bool B, class T, class F >
struct conditional;

用法比较简单:

#include <iostream>
#include <type_traits>

int main() 
{
    typedef std::conditional<true,int,float>::type A;               // int
    typedef std::conditional<false,int,float>::type B;              // float

    typedef std::conditional<(sizeof(long long) >sizeof(long double)),
    long long, long double>::type max_size_t;

    cout<<typeid(max_size_t).name()<<endl;  //long double
}

  另外一个常用的type_traits是std::decay(朽化),它对于普通类型来说std::decay(朽化)是移除引用和cv符,大大简化了我们的书写。除了普通类型之外,std::decay还可以用于数组和函数,具体的转换规则是这样的:

  先移除T类型的引用,得到类型U,U定义为remove_reference<T>::type。

  • 如果is_array<U>::value为 true,修改类型type为remove_extent<U>::type *。
  • 否则,如果is_function<U>::value为 true,修改类型type将为add_pointer<U>::type。
  • 否则,修改类型type为 remove_cv<U>::type。

std::decay的基本用法:

typedef std::decay<int>::type A;           // int
typedef std::decay<int&>::type B;          // int
typedef std::decay<int&&>::type C;         // int
typedef std::decay<constint&>::type D;    // int
typedef std::decay<int[2]>::type E;        // int*
typedef std::decay<int(int)>::type F;      // int(*)(int)

  std::decay除了移除普通类型的cv符的作用之外,还可以将函数类型转换为函数指针类型,从而将函数指针变量保存起来,以便在后面延迟执行,比如下面的例子。

template<typename F>
struct SimpFunction
{
    using FnType = typename std::decay<F>::type;//先移除引用再添加指针

    SimpFunction(F& f) : m_fn(f){}

    void Run()
    {
        m_fn();
    }

    FnType m_fn;
};

  如果要保存输入的函数,则先要获取函数对应的函数指针类型,这时就可以用std::decay来获取函数指针类型了,using FnType = typename std::decay<F>::type;实现函数指针类型的定义。type_traits还提供了获取可调用对象返回类型的元函数:std::result_of,它的基本用法:

int fn(int) {return int();}                            // function
typedef int(&fn_ref)(int);                             // function reference
typedef int(*fn_ptr)(int);                             // function pointer
struct fn_class { int operator()(int i){return i;} };  // function-like class

int main() {
  typedef std::result_of<decltype(fn)&(int)>::type A;  // int
  typedef std::result_of<fn_ref(int)>::type B;         // int
  typedef std::result_of<fn_ptr(int)>::type C;         // int
  typedef std::result_of<fn_class(int)>::type D;       // int
}

  type_traits还提供了一个很有用的元函数std::enable_if,它利用SFINAE(substitude failure is not an error)特性,根据条件选择重载函数的元函数std::enable_if,它的原型是:

template<bool B, class T = void> struct enable_if;

  根据enable_if的字面意思就可以知道,它使得函数在判断条件B仅仅为true时才有效,它的基本用法:

template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, T>::type foo(T t)
{
    return t;
}
auto r = foo(1); //返回整数1
auto r1 = foo(1.2); //返回浮点数1.2
auto r2 = foo(“test”); //compile error

  在上面的例子中对模板参数T做了限定,即只能是arithmetic(整型和浮点型)类型,如果为非arithmetic类型,则编译不通过,因为std::enable_if只对满足判断式条件的函数有效,对其他函数无效。

  可以通过enable_if来实现编译期的if-else逻辑,比如下面的例子通过enable_if和条件判断式来将入参分为两大类,从而满足所有的入参类型:

template <class T>
typename std::enable_if<std::is_arithmetic<T>::value, int>::type foo1(T t)
{
    cout << t << endl;
    return 0;
}

template <class T>
typename std::enable_if<!std::is_arithmetic<T>::value, int>::type foo1(T &t)
{
    cout << typeid(T).name() << endl;
    return 1;
}

  对于arithmetic类型的入参则返回0,对于非arithmetic的类型则返回1,通过arithmetic将所有的入参类型分成了两大类进行处理。从上面的例子还可以看到,std::enable_if可以实现强大的重载机制,因为通常必须是参数不同才能重载,如果只有返回值不同是不能重载的,而在上面的例子中,返回类型相同的函数都可以重载。

  C++11的type_traits提供了近百个在编译期计算、查询、判断、转换和选择的元函数,为我们编写元程序提供了很大的便利。如果说C++11的type_traits让模版元编程变得简单,那么C++11提供的可变模板参数和tuple则进一步增强了模板元编程。

4.可变模板参数

  C++11的可变模版参数(variadic templates)是C++11新增的最强大的特性之一,它对参数进行了高度泛化,它能表示0到任意个数、任意类型的参数。关于它的用法和使用技巧读者可以参考笔者在程序员2015年2月A上的文章:泛化之美--C++11可变模版参数的妙用,这里不再赘述,这里将要展示的如何借助可变模板参数实现一些编译期算法,比如获取最大值、判断是否包含了某个类型、根据索引查找类型、获取类型的索引和遍历类型等算法。实现这些算法需要结合type_traits或其它C++11特性,下面来看看这些编译期算法是如何实现的。

  编译期从一个整形序列中获取最大值:        

//获取最大的整数
template <size_t arg, size_t... rest>
struct IntegerMax;

template <size_t arg>
struct IntegerMax<arg> : std::integral_constant<size_t, arg>
{
};

template <size_t arg1, size_t arg2, size_t... rest>
struct IntegerMax<arg1, arg2, rest...> : std::integral_constant<size_t, arg1 >= arg2 ? IntegerMax<arg1, rest...>::value :
    IntegerMax<arg2, rest...>::value >
{
};

  这个IntegerMax的实现用到了type_traits中的std::integral_const,它在展开参数包的过程中,不断的比较,直到所有的参数都比较完,最终s

template < typename T, typename... List >
struct IndexOf;

template < typename T, typename Head, typename... Rest >
struct IndexOf<T, Head, Rest...>
{
    enum{ value = IndexOf<T, Rest...>::value+1 };
};

template < typename T, typename... Rest >
struct IndexOf<T, T, Rest...>
{
    enum{ value = 0 };
};

template < typename T >
struct IndexOf<T>
{
    enum{value = -1};
};

td::integral_const的value值即为最大值。它的使用很简单:

cout << IntegerMax<2, 5, 1, 7, 3>::value << endl; //value为7

  我们可以在IntegerMax的基础上轻松的实现获取最大内存对齐值的元函数MaxAlign。

  编译期获取最大的align:

  这个Contains的实现用到了type_traits的std::conditional、std::is_same、std::true_type和std::false_type,它的实现思路是在展开参数包的过程中不断的比较类型是否相同,如果相同则设置值为true,否则设置为false。

        编译期获取类型的索引:

template<typename... Args>
struct MaxAlign : std::integral_constant<int, IntegerMax<std::alignment_of<Args>::value...>::value>{};
cout << MaxAlign<int, short, double, char>::value << endl; //value为8
    编译判断是否包含了某种类型:
template < typename T, typename... List >
struct Contains;

template < typename T, typename Head, typename... Rest >
struct Contains<T, Head, Rest...>
    : std::conditional< std::is_same<T, Head>::value, std::true_type, Contains<T, Rest... >> ::type{};

template < typename T >
struct Contains<T> : std::false_type{};
用法:cout<<Contains<int, char, double, int, short>::value<<endl; //输出true

  用法:cout<< IndexOf<int, double, short, char, int, float>::value<<endl; //输出3

  这个IndexOf的实现比较简单,在展开参数包的过程中看是否匹配到特化的IndexOf<T, T, Rest...>,如果匹配上则终止递归将之前的value累加起来得到目标类型的索引位置,否则将value加1,如果所有的类型中都没有对应的类型则返回-1;

  编译期根据索引位置查找类型:

template<int index, typename... Types>
struct At;

template<int index, typename First, typename... Types>
struct At<index, First, Types...>
{
    using type = typename At<index - 1, Types...>::type;
};

template<typename T, typename... Types>
struct At<0, T, Types...>
{
    using type = T;
};
    用法:
using T = At<1, int, double, char>::type;
    cout << typeid(T).name() << endl; //输出double

   At的实现比较简单,只要在展开参数包的过程中,不断的将索引递减至0时为止即可获取对应索引位置的类型。接下来看看如何在编译期遍历类型。

template<typename T>
void printarg()
{
    cout << typeid(T).name() << endl;
}

template<typename... Args>
void for_each() 
{
    std::initializer_list<int>{(printarg<Args>(), 0)...};
}
用法:for_each<int,double>();//将输出int double

这里for_each的实现是通过初始化列表和逗号表达式来遍历可变模板参数的。

  可以看到,借助可变模板参数和type_traits以及模板偏特化和递归等方式我们可以实现一些有用的编译期算法,这些算法为我们编写应用层级别的代码奠定了基础,后面模板元编程的具体应用中将会用到这些元函数。

  C++11提供的tuple让我们编写模版元程序变得更灵活了,在一定程度上增强了C++的泛型编程能力,下面来看看tuple如何应用于元程序中的。

5.tuple与模版元

  C++11的tuple本身就是一个可变模板参数组成的元函数,它的原型如下:

template<class...Types>
class tuple;

  tuple在模版元编程中的一个应用场景是将可变模板参数保存起来,因为可变模板参数不能直接作为变量保存起来,需要借助tuple保存起来,保存之后再在需要的时候通过一些手段将tuple又转换为可变模板参数,这个过程有点类似于化学中的“氧化还原反应”。看看下面的例子中,可变模板参数和tuple是如何相互转换的:

//定义整形序列
template<int...>
struct IndexSeq{};

//生成整形序列
template<int N, int... Indexes>
struct MakeIndexes : MakeIndexes<N - 1, N - 1, Indexes...>{};

template<int... indexes>
struct MakeIndexes<0, indexes...>{
    typedef IndexSeq<indexes...> type;
};

template<typename... Args>
void printargs(Args... args){
    //先将可变模板参数保存到tuple中
    print_helper(typename MakeIndexes<sizeof... (Args)>::type(), std::make_tuple(args...));
}

template<int... Indexes, typename... Args>
void print_helper(IndexSeq<Indexes...>, std::tuple<Args...>&& tup){
    //再将tuple转换为可变模板参数,将参数还原回来,再调用print
    print(std::get<Indexes>(tup)...); 
}
template<typename T>
void print(T t)
{
    cout << t << endl;
}

template<typename T, typename... Args>
void print(T t, Args... args)
{
    print(t);
    print(args...);
}

  用法:printargs(1, 2.5, “test”); //将输出1 2.5 test

  上面的例子print实际上是输出可变模板参数的内容,具体做法是先将可变模板参数保存到tuple中,然后再通过元函数MakeIndexes生成一个整形序列,这个整形序列就是IndexSeq<0,1,2>,整形序列代表了tuple中元素的索引,生成整形序列之后再调用print_helper,在print_helper中展开这个整形序列,展开的过程中根据具体的索引从tuple中获取对应的元素,最终将从tuple中取出来的元素组成一个可变模板参数,从而实现了tuple“还原”为可变模板参数,最终调用print打印可变模板参数。

  tuple在模板元编程中的另外一个应用场景是用来实现一些编译期算法,比如常见的遍历、查找和合并等算法,实现的思路和可变模板参数实现的编译期算法类似,关于tuple相关的算法,读者可以参考笔者在github上的代码:cosmos/tuple at master · qicosmos/cosmos · GitHub。

  下面来看看模版元的具体应用。

6.模版元的应用

  我们将展示如何通过模版元来实现function_traits和Vairant类型。

  function_traits用来获取函数语义的可调用对象的一些属性,比如函数类型、返回类型、函数指针类型和参数类型等。下面来看看如何实现function_traits。

template<typename T>
struct function_traits;

//普通函数
template<typename Ret, typename... Args>
struct function_traits<Ret(Args...)>
{
public:
    enum { arity = sizeof...(Args) };
    typedef Ret function_type(Args...);
    typedef Ret return_type;
    using stl_function_type = std::function<function_type>;
    typedef Ret(*pointer)(Args...);

    template<size_t I>
    struct args
    {
        static_assert(I < arity, "index is out of range, index must less than sizeof Args");
        using type = typename std::tuple_element<I, std::tuple<Args...>>::type;
    };
};

//函数指针
template<typename Ret, typename... Args>
struct function_traits<Ret(*)(Args...)> : function_traits<Ret(Args...)>{};

//std::function
template <typename Ret, typename... Args>
struct function_traits<std::function<Ret(Args...)>> : function_traits<Ret(Args...)>{};

//member function
#define FUNCTION_TRAITS(...) \
    template <typename ReturnType, typename ClassType, typename... Args>\
    struct function_traits<ReturnType(ClassType::*)(Args...) __VA_ARGS__> : function_traits<ReturnType(Args...)>{}; \

FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)

//函数对象
template<typename Callable>
struct function_traits : function_traits<decltype(&Callable::operator())>{};

  由于可调用对象可能是普通的函数、函数指针、lambda、std::function和成员函数,所以我们需要针对这些类型分别做偏特化。其中,成员函数的偏特化稍微复杂一点,因为涉及到cv符的处理,这里通过定义一个宏来消除重复的模板类定义。参数类型的获取我们是借助于tuple,将参数转换为tuple类型,然后根据索引来获取对应类型。它的用法比较简单:

template<typename T>
void PrintType()
{
    cout << typeid(T).name() << endl;
}
int main()
{
    std::function<int(int)> f = [](int a){return a; };
    PrintType<function_traits<std::function<int(int)>>::function_type>(); //将输出int __cdecl(int)
    PrintType<function_traits<std::function<int(int)>>::args<0>::type>();//将输出int
    PrintType<function_traits<decltype(f)>::function_type>();//将输出int __cdecl(int)
}

  有了这个function_traits和前面实现的一些元函数,我们就能方便的实现一个“万能类型”—Variant,Variant实际上一个泛化的类型,这个Variant和boost.variant的用法类似。boost.variant的基本用法如下:

typedef variant<int,char, double> vt;
vt v = 1;
v = 'a';
v = 12.32;

  这个variant可以接受已经定义的那些类型,看起来有点类似于c#和java中的object类型,实际上variant是擦除了类型,要获取它的实际类型的时候就稍显麻烦,需要通过boost.visitor来访问:

struct VariantVisitor : public boost::static_visitor<void>
{
    void operator() (int a)
    {
        cout << "int" << endl;
    }

    void operator() (short val)
    {
        cout << "short" << endl;
    }

    void operator() (double val)
    {
        cout << "double" << endl;
    }

    void operator() (std::string val)
    {
        cout << "string" << endl;
    }
};

boost::variant<int,short,double,std::string> v = 1;
boost::apply_visitor(visitor, v); //将输出int

  通过C++11模版元实现的Variant将改进值的获取,将获取实际值的方式改为内置的,即通过下面的方式来访问:

typedef Variant<int, double, string, int> cv;
cv v = 10;
v.Visit([&](double i){cout << i << endl; }, [](short i){cout << i << endl; }, [=](int i){cout << i << endl; },[](const string& i){cout << i << endl; });

  这种方式更方便直观。Variant的实现需要借助前文中实现的一些元函数MaxInteger、MaxAlign、Contains和At等等。下面来看看Variant实现的关键代码,完整的代码请读者参考笔者在github上的代码https://github.com/qicosmos/cosmos/blob/master/Varaint.hpp。

template<typename... Types>
class Variant{
    enum{
        data_size = IntegerMax<sizeof(Types)...>::value,
        align_size = MaxAlign<Types...>::value
    };
    using data_t = typename std::aligned_storage<data_size, align_size>::type;
public:
    template<int index>
    using IndexType = typename At<index, Types...>::type;

    Variant(void) :m_typeIndex(typeid(void)){}
    ~Variant(){ Destroy(m_typeIndex, &m_data); }

    Variant(Variant<Types...>&& old) : m_typeIndex(old.m_typeIndex){
        Move(old.m_typeIndex, &old.m_data, &m_data);
    }

    Variant(const Variant<Types...>& old) : m_typeIndex(old.m_typeIndex){
        Copy(old.m_typeIndex, &old.m_data, &m_data);
    }

    template <class T,
    class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type> Variant(T&& value) : m_typeIndex(typeid(void)){
            Destroy(m_typeIndex, &m_data);
            typedef typename std::remove_reference<T>::type U;
            new(&m_data) U(std::forward<T>(value));
            m_typeIndex = type_index(typeid(U));
    }

    template<typename T>
    bool Is() const{
        return (m_typeIndex == type_index(typeid(T)));
    }

    template<typename T>
    typename std::decay<T>::type& Get(){
        using U = typename std::decay<T>::type;
        if (!Is<U>())
        {
            cout << typeid(U).name() << " is not defined. " << "current type is " <<
                m_typeIndex.name() << endl;
            throw std::bad_cast();
        }

        return *(U*)(&m_data);
    }

    template<typename F>
    void Visit(F&& f){
        using T = typename Function_Traits<F>::template arg<0>::type;
        if (Is<T>())
            f(Get<T>());
    }

    template<typename F, typename... Rest>
    void Visit(F&& f, Rest&&... rest){
        using T = typename Function_Traits<F>::template arg<0>::type;
        if (Is<T>())
            Visit(std::forward<F>(f));
        else
            Visit(std::forward<Rest>(rest)...);
    }
private:
    void Destroy(const type_index& index, void * buf){
        std::initializer_list<int>{(Destroy0<Types>(index, buf), 0)...};
    }

    template<typename T>
    void Destroy0(const type_index& id, void* data){
        if (id == type_index(typeid(T)))
            reinterpret_cast<T*>(data)->~T();
    }

    void Move(const type_index& old_t, void* old_v, void* new_v) {
        std::initializer_list<int>{(Move0<Types>(old_t, old_v, new_v), 0)...};
    }

    template<typename T>
    void Move0(const type_index& old_t, void* old_v, void* new_v){
        if (old_t == type_index(typeid(T)))
            new (new_v)T(std::move(*reinterpret_cast<T*>(old_v)));
    }

    void Copy(const type_index& old_t, void* old_v, void* new_v){
        std::initializer_list<int>{(Copy0<Types>(old_t, old_v, new_v), 0)...};
    }

    template<typename T>
    void Copy0(const type_index& old_t, void* old_v, void* new_v){
        if (old_t == type_index(typeid(T)))
            new (new_v)T(*reinterpret_cast<const T*>(old_v));
    }
private:
    data_t m_data;
    std::type_index m_typeIndex;//类型ID
};

  实现Variant首先需要定义一个足够大的缓冲区用来存放不同的类型的值,这个缓类型冲区实际上就是用来擦除类型,不同的类型都通过placement new在这个缓冲区上创建对象,因为类型长度不同,所以需要考虑内存对齐,C++11刚好提供了内存对齐的缓冲区aligned_storage:

template< std::size_t Len, std::size_t Align = /*default-alignment*/ >
struct aligned_storage;

  它的第一个参数是缓冲区的长度,第二个参数是缓冲区内存对齐的大小,由于Varaint可以接受多种类型,所以我们需要获取最大的类型长度,保证缓冲区足够大,然后还要获取最大的内存对齐大小,这里我们通过前面实现的MaxInteger和MaxAlign就可以了,Varaint中内存对齐的缓冲区定义如下:

enum
{
    data_size = IntegerMax<sizeof(Types)...>::value,
    align_size = MaxAlign<Types...>::value
};
using data_t = typename std::aligned_storage<data_size, align_size>::type; //内存对齐的缓冲区类型

  其次,我们还要实现对缓冲区的构造、拷贝、析构和移动,因为Variant重新赋值的时候需要将缓冲区中原来的类型析构掉,拷贝构造和移动构造时则需要拷贝和移动。这里以析构为例,我们需要根据当前的type_index来遍历Variant的所有类型,找到对应的类型然后调用该类型的析构函数。

void Destroy(const type_index& index, void * buf)
    {
        std::initializer_list<int>{(Destroy0<Types>(index, buf), 0)...};
    }

    template<typename T>
    void Destroy0(const type_index& id, void* data)
    {
        if (id == type_index(typeid(T)))
            reinterpret_cast<T*>(data)->~T();
    }

  这里,我们通过初始化列表和逗号表达式来展开可变模板参数,在展开的过程中查找对应的类型,如果找到了则析构。在Variant构造时还需要注意一个细节是,Variant不能接受没有预先定义的类型,所以在构造Variant时,需要限定类型必须在预定义的类型范围当中,这里通过type_traits的enable_if来限定模板参数的类型。

template <class T,
    class = typename std::enable_if<Contains<typename std::remove_reference<T>::type, Types...>::value>::type> Variant(T&& value) : m_typeIndex(typeid(void)){
            Destroy(m_typeIndex, &m_data);
            typedef typename std::remove_reference<T>::type U;
            new(&m_data) U(std::forward<T>(value));
            m_typeIndex = type_index(typeid(U));
    }

  这里enbale_if的条件就是前面实现的元函数Contains的值,当没有在预定义的类型中找到对应的类型时,即Contains返回false时,编译期会报一个编译错误。

  最后还需要实现内置的Vistit功能,Visit的实现需要先通过定义一系列的访问函数,然后再遍历这些函数,遍历过程中,判断函数的第一个参数类型的type_index是否与当前的type_index相同,如果相同则获取当前类型的值。

template<typename F>
    void Visit(F&& f){
        using T = typename Function_Traits<F>::template arg<0>::type;
        if (Is<T>())
            f(Get<T>());
    }

    template<typename F, typename... Rest>
    void Visit(F&& f, Rest&&... rest){
        using T = typename Function_Traits<F>::template arg<0>::type;
        if (Is<T>())
            Visit(std::forward<F>(f));
        else
            Visit(std::forward<Rest>(rest)...);
    }

  Visit功能的实现利用了可变模板参数和function_traits,通过可变模板参数来遍历一系列的访问函数,遍历过程中,通过function_traits来获取第一个参数的类型,和Variant当前的type_index相同的则取值。为什么要获取访问函数第一个参数的类型呢?因为Variant的值是唯一的,只有一个值,所以获取的访问函数的第一个参数的类型就是Variant中存储的对象的实际类型。

7总结

  C++11中的一些特性比如type_traits、可变模板参数和tuple让模版元编程变得更简单也更强大,模版元编程虽然功能强大,但也比较复杂,要用好模版元,需要我们转变思维方式,在掌握基本的理论的基础上,再认真揣摩模版元的一些常用技巧,这些技巧是有规律可循的,基本上都是通过重定义、递归和偏特化等手法来实现的,当我们对这些基本技巧很熟悉的时候再结合不断地实践,相信对模版元编程就能做到“游刃有余”了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/510080.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

找嵌入式软件工作,freertos要掌握到什么程度?

作为一个在嵌入式软件行业工作的6年的工程师告诉你&#xff0c;这根本不是重点&#xff0c;我没有实际学过RTOS&#xff0c;都是赶鸭子上架&#xff0c;只要你会单片机&#xff0c;这种东西太简单了。在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「fre…

Rust 机器学习图形库 petgraph

一、介绍 Petgraph 是一个开源的图数据结构库&#xff0c;提供了非常丰富的图形类型和算法&#xff0c;并且支持将图形以 Graphviz 格式输出&#xff0c;还允许你为图的节点和边赋予任意类型的数据&#xff0c;从而能够灵活地处理和表示复杂的数据关系。 Petgraph 支持边的方…

Day15_学点儿JavaScript_数据类型、关系运算符、String、Boolean、Array、Math、eval

1 JS简介 1.1 JS 1、直接嵌入HTML页面。 2、由浏览器解释执行代码&#xff0c;不进行预编译。 1.2 解释型语言和编译型语言 JS&#xff1a;解释型语言、弱类型语言 Java&#xff1a;编译型语言、强类型语言 变量: var num 100; variable 用var来定义一个变量。 int num …

某盾滑块拼图验证码增强版

介绍 提示&#xff1a;文章仅供交流学习&#xff0c;严禁用于非法用途&#xff0c;如有不当可联系本人删除 最近某盾新推出了&#xff0c;滑块拼图验证码&#xff0c;如下图所示&#xff0c;这篇文章介绍怎么识别滑块距离相关。 参数attrs 通过GET请求获取的参数attrs, 决…

虚拟机安装银河麒麟

背景 由于Centos将于2024-06-30结束维护【脱保】&#xff0c;届时会存在Bug无人修复及功能无人开发等问题&#xff0c;所以要赶在这个节点前完成操作系统升级。可选的就是RedHat、Ubuntu以及国产信创【中标麒麟、银河麒麟、统信等】&#xff0c;或者使用云上操作系统【例如租阿…

九泰智库 | 医械周刊- Vol.19

⚖️ 法规动态 国创中心联合中科院发布《高性能医疗器械2023年度发展报告》 近日&#xff0c;国家高性能医疗器械创新中心和中国科学院深圳先进技术研究院共同发布了《高性能医疗器械2023年度发展报告》。该报告指出&#xff0c;我国医疗器械领域已形成以粤港澳大湾区、长三角…

号称史上最全的PostgreSQL备份恢复,送给有缘人...

&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&#x1f61c;&#x1f61c; 中国DBA联盟(ACD…

开抖店内行人都不说的秘密:这几个细节新手必犯!干货建议收藏!

哈喽~我是电商月月 开抖音小店必交的钱就是店铺的保证金 虽说店铺保证金最后店铺不开了&#xff0c;保证金还能退回 但只有开始运作时才知道&#xff0c;中间扣除你保证金的点有很多&#xff0c;要想继续开抖音小店&#xff0c;就得把扣的保证金补齐才能继续运行 但新手开店…

PostgreSql 01 安装,创建用户,创建数据库,创建关系

01.安装postgresql postgreSql 是在linux上 去官网找按照的方式 选择好postgreSql 的版本&#xff0c;已经Linux的发行版本 拿到命令&#xff0c;直接扔到Linux中运行即可 # 下载PGSQL的rpm包 sudo yum install -y https://download.postgresql.org/pub/repos/yum/reporpm…

《web应用技术》第一次课后练习

上机任务&#xff08;利用好chatgpt&#xff0c;文心一言等工具。&#xff09;&#xff1a; 1、下载软件&#xff0c;并安装。相关安装文件已上传至群文件。 JDK,TOMCAT&#xff0c;IDEA 2、学会用记事本编写jsp文件&#xff0c;并放进tomcat的相关目录下&#xff0c;运行。 …

vSAN vs SmartX 超融合:BI 数仓跑批效率对比测试

在《VMware 与 SmartX 分布式存储缓存机制浅析与性能对比》中&#xff0c;我们分析了 vSAN 7 缓存击穿的问题及其原因。近期&#xff0c;某金融用户在进行数据仓库分布式存储选型时&#xff0c;同样遭遇了测试过程中 vSAN 出现缓存击穿而难以完成全量跑批任务的问题。随后&…

时序数据库IoTDB:功能详解与行业应用

一文读懂时序数据库 IoTDB。 01 为什么需要时序数据库 解释时序数据库前&#xff0c;先了解一下何谓时序数据。 时序数据&#xff0c;也称为时间序列数据&#xff0c;是指按时间顺序记录的同一统计指标的数据集合。这类数据的来源主要是能源、工程、交通等工业物联网强关联行业…

【算法】二分查找(红绿灯法)

引言 该方法来自b站算法大师兄&#xff0c;可用作通用模版处理二分查找问题&#xff0c;不用特意考虑边界临界值等情况。 方法描述 红色节点是小于target&#xff0c;绿色节点是大于等于target。 我们首先定义两个下标代表左和右&#xff0c;分别为-1和n。然后用红箭头和绿箭…

如何在Linux系统运行RStudio Server并实现无公网IP远程访问【内网穿透】

文章目录 推荐 前言1. 安装RStudio Server2. 本地访问3. Linux 安装cpolar4. 配置RStudio server公网访问地址5. 公网远程访问RStudio6. 固定RStudio公网地址 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下…

干货必读: 测试开发既然都这么厉害了!为啥不直接转业务开发?

前段时间&#xff0c;在后台收到一则留言&#xff1a;“请问一下&#xff0c;你觉得开发技术好&#xff0c;还是测试技术好&#xff0c;如果测试技术好&#xff0c;为什么不直接开发&#xff0c;干嘛做测试&#xff1f;” 这是一则很有意思且大多数技术新人普遍存在的困惑&…

kubernetes-dashboard 安装配置

k8s 1.23以上的版本 https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml 执行命令&#xff1a; kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.7.0/aio/deploy/recommended.yaml 安装完成后&#x…

[问题记录] oracle问题汇总记录

plsql问题 1、oracle-initialization error could not locate OCI.dll 下载plsql客户端后&#xff0c;登录显示如图所示的错误 解决方法&#xff0c;点击下方链接&#xff0c;下载64位客户端 Instant Client for Microsoft Windows (x64) 64-bit (oracle.com) 2、显示中文乱…

百度语音识别

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、建号—获取试用KEY二、测试代码三、运行四、运行结果五、验证五、总结 一、建号—获取试用KEY https://console.bce.baidu.com/ai/#/ai/speech/overview/index…

【Spring】SpringBoot整合MybatisPlus的基本应用

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 一、MybatisPlus简介 先来看一下官方的简介吧。 MyBatis-Plus &#xff08;简称 MP&#xff09;是一个 MyBatis的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为 简化开发、提高效率而生。Myb…