<C++ 模板元编程> 编程技巧
基于 Walter E. Brown 的演讲 - Modern Template Metaprogramming (2)
示例代码可以贴到
www.onlinegdb.com
直接运行,需要选择C++17
一些常用的alias
使用这些 alias 可以简化编程
template< typename T, T v >
struct integral_constant { static constexpr T value = v; };
template< bool b >
using bool_constant = integral_constant< bool, b >;
using true_type = bool_constant< true >; // 表示有 ::value == true
using false_type = bool_constant< false >;
通过 is_void 的几种实现,讲解常见的元编程方法
1. 通过继承和特化 - inheritance + specialization
#include <type_traits>
template< typename T > struct is_void : std::false_type {};
template<> struct is_void< void > : std::true_type {};
template<> struct is_void< void const > : std::true_type {};
template<> struct is_void< void volatile > : std::true_type {};
template<> struct is_void< void const volatile > : std::true_type {};
int main() {
static_assert(is_void< void >::value == true);
static_assert(is_void< int >::value == false);
return 0;
2. 委托给其他代理类
#include <type_traits>
template< typename T, typename U > struct is_same : std::false_type {};
template< typename T > struct is_same< T, T > : std::true_type {};
template< typename T >
using is_void = is_same< std::remove_cv_t< T >, void >;
int main() {
static_assert(is_void< void >::value == true);
static_assert(is_void< int >::value == false);
return 0;
3. 使用多个参数进行推导 - parameter pack
#include <type_traits>
template< typename T, typename... P0toN >
struct is_one_of;
template< typename T >
struct is_one_of< T > : std::false_type {};
template< typename T, typename... P1toN >
struct is_one_of< T, T, P1toN... > : std::true_type {}; // 前两个参数相同,返回 true
template< typename T, typename P0, typename... P1toN >
struct is_one_of< T, P0, P1toN... > : is_one_of< T, P1toN... > {}; // 前两个参数不同,那么跳过 P0
template< typename T >
using is_void = is_one_of< T, void, void const, void volatile, void const volatile >;
int main() {
static_assert(is_void< void >::value == true);
static_assert(is_void< int >::value == false);
return 0;
非求值操作 - unevaluated operands
sizeof, typeid, decltype, noexcept 即使在编译时,也是不会进行求值操作的,不会直接产生对应机器码。这意味着,对于这些操作,只需要提供声明就可以了,并不需要提供完整的定义信息。
下面的代码生成一个 T 类型的右值变量,作为参数传递给 foo,最后获取 foo 的返回值的类型
decltype( foo( declval< T >() ) )
declval的用法
std::declval< T >() // 返回 T 的 rvalue
std::declval< T& >() // 返回 T 的 lvalue
检测某种类型是否可以进行拷贝赋值操作的元函数
#include <iostream>
#include <type_traits>
template< typename T >
struct is_copy_assignable {
private:
template< typename U, typename = decltype(std::declval< U& >() = std::declval< U const& >())>
static std::true_type try_assignment(U&&); // #1 函数模板的声明,可能会有SFINAE
static std::false_type try_assignment(...); // 优先级最低的重载函数,非模板
public:
using type = decltype(try_assignment(std::declval< T >())); // #2,#3
struct A {
A(const A&) = delete;
A& operator=(const A&) = delete;
int main() {
std::cout << is_copy_assignable< int >::type::value; // output 1
std::cout << is_copy_assignable< A >::type::value; // output 0
return 0;
这个写法并没有限制拷贝赋值操作的返回值必须是 T&,后面有另外一种写法修正了这个问题,确保赋值操作的返回值必须是 T&
#1、这里第二个模板参数没有名字,仅仅是做测试用的
std::declval< U&>()= std::declval< U const&>()
,右侧似乎写成
<U&>
或者
<U>
也是可以的
试图将一个左值常量赋给一个左值变量
如果该类型确实存在赋值操作
那么
decltype
可以取到一个明确的类型
进而成功匹配这个模板函数,最后返回 true_type
否则,SFINAE 界入,
这个模板函数失效 自动匹配到下一个函数上,返回 false_type
#2、
declval<T>()
造出一个 T 类型的右值变量
这里似乎也可以写成 T& 给出左值变量
将该变量作为参数传给
try_assignment
函数
然后获取这个函数的返回值 根据上面的定义,返回值只可能是 true 或者 false
#3、在 C++11 推出 decltype 之前,这里会使用另外一个技巧来判断 true 和 false
通过定义不同大小的 struct,然后通过 sizeof 检查 struct 的大小,来判断返回的类型
这是旧的方法,可能在旧的代码中看到,能读懂就可以了。
新的代码,需要用新的方式写。
typedef char(&yes)[1];
typedef char(&no)[2];
sizeof( try_assignement(...) )
typedef boo_constant< sizeof( try_assignment ) == sizeof(yes) > type;
下面是别的地方看到的,另外一种实现
#include <iostream>
template< typename, typename = void >
struct is_copy_assignable : std::false_type {};
template< typename T >
struct is_copy_assignable< T, decltype(std::declval<T&>() = std::declval<const T&>(), void())> : std::true_type {};
struct A {
A(const A&) = delete;
A& operator=(const A&) = delete;
int main() {
std::cout << is_copy_assignable< int >::value; // output 1
std::cout << is_copy_assignable< A >::value; // output 0
return 0;
检查是否存在 T::type 的元函数
#include <type_traits>
template< typename ... >
using void_t = void; // 看似没做什么的代码,但是非常有用
template< typename, typename = void >
struct has_type_member : std::false_type {};
template< typename T >
struct has_type_member< T, void_t< typename T::type > > : std::true_type {}; // #1
struct A {
using type = double;
int main() {
static_assert(has_type_member< A >::value == true);
static_assert(has_type_member< int >::value == false);
return 0;
#1 如果确实存在 T::type
那么 void_t< typename T::type > 语法正确
根据定义返回 void 进而匹配这个模板函数,返回 true
否则会导致 SFINAE 界入 匹配到 false_type 的实现上
检测某种类型是否可以进行拷贝赋值操作的元函数,另外一种实现
#include <iostream>
template< typename ... >
using void_t = void; // 看似没做什么的代码,但是非常有用
template< typename T >
using copy_assignment_t = decltype( std::declval< T& >() = std::declval< T const& >() );
template< typename T, typename = void >
struct is_copy_assignable : std::false_type {};
template< typename T >
struct is_copy_assignable< T, void_t< copy_assignment_t< T > > > : std::is_same< copy_assignment_t< T >, T& > {};
// 只有 T 存在赋值操作时,copy_assignment_t< T > 才有正确的解析,否则就是 SFINAE
// 通过 is_same 检查了赋值操作的返回值必须是 T&
struct A {
A(const A&) = delete;
A& operator=(const A&) = delete;
int main() {