看了些有关模板元编程的东西, 写点笔记
定义
元编程指通过编写或操纵其它程序或自身作为数据, 或者在运行时完成部分本该在编译时完成的工作的编程方式. (来自Wikipedia)
C++中的模板元编程(Template MetaProgramming, TMP)使用模板的实例化来进行编译期的求值.
模板元编程通常用来提高代码的灵活性和提升运行时速度(因为一部分计算放到了编译期中).
模板元编程是图灵完备的, 通过模板特化实现分支, 通过递归实现循环, 同时由于是编译期的求值, 其中的参数, 中间量和返回值均是不可变的. 从这些角度来说模板元编程有点类似于函数式编程.
一些例子
求两个数的最大公因数gcd<m, n>
1
2
3
4
5
6
7
8
9template<unsigned m, unsigned n>
struct gcd {
static unsigned constexpr value = gcd<n, m%n>::value;
};
template<unsigned m>
static gcd<m, 0> {
static_assert(m != 0);
static unsigned constexpr value = m;
}
返回值为值的元函数也可以使用constexpr
函数代替:1
2
3constexpr unsigned gcd(unsigned a, unsigned b) {
return b == 0 ? a : gcd(b, a%b);
}
获得去除const
的类型remove_const<T>
1
2
3
4template<class T>
struct remove_const { using type = T; }
template<class T>
struct remove_const<T const> { using type = T; }
获得类型本身identity<T>
1
2template<class T>
struct identity<T> { using type = T; }
获得某类型的一个值integral_constant<T, v>
1
2
3
4
5
6
7template<class T, T v>
struct integral_constant {
static constexpr T value = v;
constexpr operator T () const noexcept { return value; }
constexpr T operator ()() const noexcept { return value; }
...;
};
bool_constant<b>
1
2template<bool b>
using bool_constant = integral_constan<bool, b>;
true_type
和false_type
1
2using true_type = bool_constant<true>
using false_type = bool_constant<false>
这两个类型可以简化元函数的编写, 例如:
判断两个类型是否一致is_same<T, U>
1
2
3
4template<class T, class U>
struct is_same : false_type {};
template<class T>
struct is_same<T, T> : true_type {};
判断某类型是否在类型列表中is_one_of<T, Ts...>
1
2
3
4
5
6
7template<class T, class... Ps> struct is_one_of;
template<class T>
struct is_one_of<T> : false_type {};
template<class T, class... Ps>
struct is_one_of<T, T, Ps...> : true_type {};
template<class T, class P0, class... Ps>
struct is_one_of<T, P0, Ps...> : is_one_of<T, Ps...> {};
判断类型是否为void
类型is_void<T>
(type trait)1
2
3
4
5template<class 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 const volatile> : true_type {};
或者:1
2
3
4
5
6template<class T> struct is_void :
is_one_of<T,
void,
void const,
void volatile,
void const volatile> {};
若定义了remove_cv<T>
(去除const
和volatile
)也有:1
2
3template<class T> struct is_void :
is_same<remove_cv_t<T>, void>
{};
头文件<type_traits>
中定义了许多类似的type trait, 由于性能原因绝大多数type trait的实现都是基于编译器的内建方法.
元函数的调用
通过获取结构体中的公共成员以”调用”元函数.
1 | gcd<8, 12>::value; |
由于模板可被重载, 需要使用typename
关键字指定::type
为类型.
一些约定
返回值为值的模板函数, 返回值放在公开的静态成员常量value
中, 或者直接继承自std::integral_constant
.
返回值为类型的模板函数, 返回值放在公开的成员类型type
中.
一般需要一个以_v
(值)或_t
(类型)结尾的的模板常量以方便调用:
1 | template<unsigned m, unsigned n> |
不要对标准库中的type trait特化, 这样可能导致未定义行为.
SFINAE
SFINAE means Substitution Failure Is Not An Error.
当模板实例化时, 编译器首先会寻找模板类型参数, 有如下三种情形:
- 使用显式的模板类型参数;
- 从函数的实参中推断类型;
- 使用默认的模板类型参数.
找到模板参数后则会依次查找模板的定义, 使用找到的模板参数替换定义中的模板参数. 若替换过程中产生的代码有意义则实例化成功, 否则称发生了替换失败(Substitution Failure), 编译器不会抛出错误, 将继续寻找其它定义.
假设定义了元函数enable_if<b, T>
1 | template<bool, class T = void> |
注意false时结构体无type
成员.
假设某算法f
需要对整型与浮点型数据进行重载, 则有:
1 | template<class T> |
若传入的参数为整型则第二条中的enable_if_t
将生成错误的代码(对无type
成员的结构体取type
), 发生SFINAE, 寻找其它定义, 反之亦然. 但当传入的参数为其它的类型(例如字符串), 则两个定义均不成立, 将发生编译错误.
C++20以后可以使用concept和requires子句替代上述写法:
1
2
3
4
5 template<integral T> // concept
int f (T val) {...;}
template<class T>
requires floating_point<T> // requires clause
double f (T val) {...;}
非求值运算
对于例如sizof
, decltype
之类的运算符, 其运算对象在编译期和运行时均不求值. 这意味着这类运算符只会检查运算对象的声明.
其中decltype()
和std::declval<T>()
是一对十分有用的非求值运算. decltype()
将返回括号内表达式的返回值类型, std::declval<T>()
将返回一个类型为T
的右值引用(std::declval<T&>()
可返回左值引用, 由于引用折叠).
用例: 判断某类型是否能够复制赋值is_copy_assignable<T>
1
2
3
4
5
6
7
8
9
10template<class T>
struct is_copy_assignable {
private:
template<class U,
class = decltype(declval<U&>() = declval<U const&>())>
static true_type try_assignment(U&&);
static false_type try_assignment(...);
public:
using type = decltype(try_assignment(declval<T>()));
};
在没有decltype
时(C++11以前)则需要使用sizeof
, 通过返回类型的大小来区分真假.
void_t
void_t
的定义如下:1
template<class...> using void_t = void;
当模板参数均有意义时则返回void
类型, 否则将引发SFINAE.
用例: 判断某类型是否有公开成员类型type
:1
2
3
4template<class T, class = void>
struct has_type : false_type {};
template<class T>
struct has_type<T, void_t<typename T::type>> : true_type {};
用例: 重写is_copy_assinable
1
2
3
4
5
6
7
8
9template<class T>
using copy_assignment_t =
decltype(declval<T&>() = declval<T const &>())
template<class T, class = void>
struct is_copy_assignable : false_type {};
template<class T>
struct is_copy_assignable<T,
void_t<copy_assignment_t<T>>> :
is_same<copy_assignment_t<T>, T&> {};
注意模板默认参数必须与void_t
的返回类型匹配, 若不匹配模板特化将失效.
在C++20以后也可以使用concept与requires子句替代:
1
2
3
4
5
6
7
8 template<class T>
concept copy_assignable = requires(T& a, T const & b)
{ a = b; };
template<class T>
struct is_copy_assignable : false_type {};
template<class T>
requires copy_assignable<T>
struct is_copy_assignable<T> : true_type {};