引言
今天看了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&比较是否类型相等
}
|