Featured image of post 打开模板元编程的大门

打开模板元编程的大门

模板元编程

引言

​ 今天看了Cpp con 2014 - Walter E. Brown博士讲的有关模板元编程的讲座,感觉打开了新世界的大门,以前虽然也看了些和模板有关的代码,但一直没有系统的了解过,而今天看完讲座,感觉串通了很多东西,也开始了解模板元编程。所以写者想在这里记录一些讲座上的代码,以及自己的一些思考。

模板元编程(TMP)

​ 普通的编程操作的对象都是数据,而在模板元编程当中,我们可以对类型进行操作。就拿普通函数和元函数进行比较,元函数不在是一个普通函数的形式,通常是一个模板类。普通函数的参数对应了元函数的函数模板,操作的对象也由数据变成了类型。普通函数的返回值与元函数中通常定义公开的type类型和value值形成了对应。

模板也可以递归

​ 以前一直以为,递归只能再函数中实现,没想到也能在模板中进行递归。不过再一想,这里用到都是元函数,那么可以用递归也合理了很多。下面两个例子展现了两种递归的实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
namespace yc {
     // 计算gcd
    // 在主模板中调用了递归,特化作为递归出口
    template <unsigned M, unsigned N>
    struct gcd {
        static constexpr int value = gcd<N,M%N>::value;
    };

    template<unsigned M>
    struct gcd<M,0> {
        static_assert(M != 0);
        static constexpr int value = M;
    };

    // rank, rank的主要作用是返回数组的维度,rank<int[10][20][30]>::value 的值是3
    // 在特化中调用了递归,主模板作为递归出口
    template<typename T>
    struct rank { static constexpr size_t value = 0u;};

    template<typename U, size_t N>
    struct rank<U[N]> {
        static size_t const value = 1u + rank<U>::value;
    };

}

::type

​ 模板元编程的操作对象是类型,下面就来看看对类型的操作吧。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
namespace yc{
    // remove_const
    template<typename T>
    struct remove_const {using type = T;};

    template<typename T>
    struct remove_const<T const> {using type = T;};	// 特化去除类型的const属性,下面去除volatile也是如此

    // 一些元函数可以继承type_is, 保证元函数的一些规范,一定会有名为type的类型
    template<typename T>
    struct type_is { using type = T;};

    template<typename T>
    struct remove_volatile : type_is<T> {};

    template<typename U>
    struct remove_volatile<U volatile> : type_is<U> {};

    // IF编译器决定调用哪一个,因为是模板,所以T和F可以是任意类型,包括函数,类。
    // 可以通过这种方式,让编译器选择将要执行的函数类型,以及将要继承的基类。
    template<bool, typename T, typename>
    struct IF : type_is<T> {};

    template<typename T, typename F>
    struct IF<false,T,F> : type_is<F> {};
}

SFINAE

​ 模板实例化需要经过替换的过程,在替换的过程中,如果失败,并不是错误,而是会放弃这次替换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
namespace yc{	
	// enable_if, SFINAE的实用工具
    template<bool, typename T = void>
    struct enable_if : type_is<T> {};

    template<typename T>
    struct enable_if<false,T> {};   // 当传入为false的时候,不给::type,用来后面enable_if_t发生SFINAE

    template<bool b, typename T>
    using enable_if_t = typename enable_if<b, T>::type;	// 通常后缀_t 表示 ::type

    //SFINAE
    // 当条件是false的时候,enable_if_t会发生错误,因为enable_if没有type类型,发生SFINAE
    template<typename T>
    enable_if_t<is_integral_v<T>, size_t> f(T val) { return val; }

    template<typename T>
    enable_if_t<is_floating_point_v<T>, long double> f(T val) { return val; };
}

::value

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace yc{
	// ::value
    template<typename T, T v>
    struct integral_constant {	// 这里T类型位置,考虑换一个名字
        static constexpr T value = v;
        
        // 允许将integral_constant 转为T类型 或者调用获得类型T
        constexpr explicit operator T() const noexcept { return value; }    
        constexpr T operator()() const noexcept { return value; }
    };

    // 重构rank
    template<typename T>
    struct rank2 : integral_constant<size_t, 0u> {};

    template<typename U, size_t N>
    struct rank2<U[N]> : integral_constant<size_t, 1u + rank2<U>::value> {};

	// 之前的rank没有考虑过的情况
    template<typename U>
    struct rank2<U[]> : integral_constant<size_t, 1u + rank2<U>::value> {};

    // 一些模板aliases
    template<bool b>
    using bool_constant = integral_constant<bool, b>;
    using true_type = bool_constant<true>;
    using false_type = bool_constant<false>;
}

更多的类型判断

​ 在这里补充一下模板实例化中有关特化的执行过程

​ 在实例化中,编译器会根据主模板,将所有类型确定下来,然后去特化中找严格匹配的类型,如果特化中没有严格匹配的类型,就不会走特化版本,而是走主模板的版本。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
namespace yc{
	// 判断一个类型是否是void类型
    // 写者在这里进行了总结,一般主模板都给一个false的value,在特化中将满足条件的特化给予一个为true的value
    template<typename T> struct is_void : false_type {};

    template<> struct is_void<void> : true_type {};
    template<> struct is_void<void const> : true_type {};
    template<> struct is_void<void volatile> : true_type {};
    template<> struct is_void<void volatile const> : true_type {};

    // 判断两个类型是否相等
    template<typename T, typename U> struct is_same : false_type {};
    template<typename T> struct is_same<T,T> : true_type {};

    template<typename T>
    using remove_cv = remove_volatile<remove_const_t<T>>;

    template<typename T>
    using remove_cv_t = typename remove_cv<T>::type;

    // 重构is_void2
    template<typename T>
    using is_void2 = is_same<remove_cv_t<T>, void>;

    // 判断类型是不是后面类型列表里面的类型
    template<typename T, typename... P0ToN>
    struct is_one_of;	// 主模板声明,用来确定类型,而下面的特化都一定能走到,所以不用写主模板

    template<typename T>
    struct is_one_of<T> : false_type {};

    template<typename T, typename... P1ToN>
    struct is_one_of<T,T,P1ToN...> : true_type {};

    template<class T, typename P0, typename... P1toN>
    struct is_one_of<T, P0, P1toN...> : is_one_of<T, P1toN...> {};
    // 模板参数列进行递归操作很常见,但这里尽然进行了一个递归的继承,递归也能玩这么六,太精彩了~~~

    // 重构is_void3
    template<typename T>
    using is_void3 = is_one_of<T, void, void const, void volatile, void volatile const>;
}

decltype和declval

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
namespace yc {
    // decltype 和 declval
    // declval 假装给了这个类型的一个值。
    // 下面假装是一个函数调用,但是是编译期并不会实际执行函数调用,只是为了找到这个函数来获取返回值类型。
    template<typename T>
    decltype(foo(declval<T>())) t;
    // declval<T&>() 给的是一个右值

    // is_copy_assignable
    template<class T>
    struct is_copy_assignable {
    private:
        template<typename U, typename = decltype(declval<U&>() = declval<U const&>())>
        static true_type try_assignment(U&&);	// 可能会触发SFINAE

        static false_type try_assignment(...);

    public:
        using type = decltype(try_assignment(declval<T>()));
    };
}

void_t

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
namespace yc{
    // void_t
    template<typename...>
    using void_t = void;

    // 查看元函数是否有type成员
    // 为什么typename=void 必须是void, 改成int会发生什么?
  	// 改成int,编译器先看主模板,实例化为has_type_member<SomeType, int>,再尝试匹配偏特化会失败。
    // 特化类型必须精确匹配,才能走特化版本,否则不会
    // 如果想要改成int,上面的也要改成void_t = int
    template<typename, typename = void>     // 这里的void一定要和void_t里的类型相匹配,选择void是一种实现方式
    struct has_type_member : false_type {}; // 类型匹配优先走偏特化,即使这两个看起来一样

    template<typename T>
    struct has_type_member<T,void_t<typename T::type>> : true_type{};

    // is_copy_assignable优化
    template<typename T>
    using copy_assignable_t = decltype(declval<T&>() = declval<T const&>());
    // T const& -> T&& 就可以实现能否进行移动的判断

    template<typename T, typename = void>
    struct is_copy_assignable2 : false_type {};

    template<typename T>
    struct is_copy_assignable2<T, void_t<copy_assignable_t<T>>> : is_same<copy_assignable_t<T>,T&>{};
    // 拷贝后的类型应当是T&, 所以需要和T&比较是否类型相等
}