2014年底才看到github和channel9上有CppCon2014的视频和资料,顿时激动不已。最近小生也一直在研习CppCon2014中令人兴奋的内容。这篇鄙文就是小生学习了《Modern Template Metaprogramming》之后,有对Unevaluated Operands更深刻的理解,有感而写。

C++98标准中的Unevaluated Operands,只有sizeof操作符。C++11又引入了decltype,typeid和noexcept。Unevaluated Operands不会有求值的计算,即使是在编译期间。这意味着Unevaluated Operands中的表达式甚至不会生成具体的C++代码,操作符中的表达式仅需要声明而无需定义。在模板元编程中,我们有时候经常仅需要对catch a glimpse of一个表达式,而这些操作符都是很好的工具。

下面我们以std::is_copy_assignable为例来看看Unevaluated Operands的威力。

namespace cpp11
{
template <typename T>
struct is_copy_assignable
{
private:
template <typename U, typename = decltype(std::declval<T&>() = std::declval<T const&>())>
static std::true_type try_assign(U&&); static std::false_type try_assign(...); public:
using type = decltype(try_assign(std::declval<T>()));
};
}

我们以c++11的版本开始。这个std::is_copy_assignable的实现原理就是SFINAE。给定一个C++类型T,如果T存在一个有效的operator=,那么成员模板函数try_assign的第二个模板参数中decltype里面的表达式将是well formed,decltype将会推导出这个表达式的类型。而第二个非模板的普通成员函数try_assign的形参使用" ..." 是因为这种情况是最差匹配的情况,is_copy_assignable需要优先匹配成员模板函数try_assign。元函数返回type时,再次使用了decltype,这里是真正的点睛之笔。由于decltype无需求值,因此try_assign无需一个函数体。std::declval是C++11引入universal reference的一个附属品,它也是一个模板函数同样没有函数体,返回值的类型是一个T&&(universal reference),try_assign中使用std::declval可以让编译器知道我们传递给这个函数的参数类型,但没有真正的去构造一个对象,就好像是假设我传递一个实参给try_assign. 在Unevaluated Operands中的表达式如果是一个函数调用,那么std::declval是一个非常好的工具,姑且可以将它当做一个惯用法。那么如果这个T不存在一个有效的operator=,编译器将使用SFINAE选择普通的成员函数。最后元函数返回的type在T::operator= 存在的情况下是std::true_type,反之则是false_type.

在c++98的年代,Unevaluated Operands只有sizeof,那么怎么实现呢。同使用下面这个例子或者类似的实现方法。

namespace cpp98
{
namespace detail
{
template <typename T>
T declval();
} template <typename T>
struct is_copy_assignable
{
private: typedef char One;
typedef struct { char a[]; } Two; template <int N = sizeof(detail::declval<T&>() = detail::declval<T const&>())>
static One try_assign(int); static Two try_assign(...); public: typedef typename std::conditional<sizeof(try_assign()) == sizeof(One),
std::true_type, std::false_type>::type type;
};
}

sizeof只能推导出类型的大小,因此try_assign不能再返回std::true_type和st::false_type,而是返回了两个大小不同的类型。实现原理依旧是SFINAE,而元函数返回的类型是根据try_assign返回类型的大小来决定导出std::true_type还是std::false_type.由于cpp98没有右值引用,所以这里自己实现了一个declval的替代版。在c++98的版本中,元函数的类型计算都是由sizeof来驱动的。c++11引入了更多的非求值操作符后,元编程确实方便了不少。

在《Modern Template Metaprogramming》这篇演讲中还有一个非常有趣的模板工具,就是void_t.  它的实现可能如下代码所示

template <typename ... Args>
struct make_void
{
typedef void type;
}; template <typename ... Args>
using void_t = typename make_void<Args...>::type;

或者,

template <typename ... Args>
using void_t = void;

让我们先来看看使用void_t来实现我们的is_copy_assignable.

namespace cpp1y
{
template <typename ... Args>
struct make_void
{
typedef void type;
}; template <typename ... Args>
using void_t = typename make_void<Args...>::type; template <typename T, typename = void>
struct is_copy_assignable : std::false_type
{}; template <typename T>
struct is_copy_assignable<T, void_t<decltype(std::declval<T&>() = std::declval<T const&>())>> : std::true_type
{
};
}

利用void_t实现的版本又要比使用SFINAE的版本实现简单许多。给定一个C++类型T,传递到元函数is_copy_assignable中,由于这个版本的元函数有两个参数,那么is_copy_assignable<T>被推导为is_copy_assignable<T,void>. 接下来,编译要检查是否有more specialized的特化版本。如果T有一个有效的operator=,那么is_copy_assignable的特化版本中的第二个参数中的decltype推导的表达式是well formed,那么decltype计算类型并与void_t共同推导出void. 由于特化版本是more specialized,因此编译器选择这个特化版本,并调用元函数std::true_type。反之,如果T没有一个有效的operator=,那么decltype推导的表达式无效,因此这种情况下的is_copy_assignable没有特化版本并调用元函数std::false_type。这里要注意的是,第二个默认的模板参数必须要以void为缺省的实参类型,因为void_t实际上就是void,才能让编译器找到特化的版本。换而言之,缺省的类型要与void_t一致,如果实现一个类似的int_t,那么这个缺省的类型应该是int.

小生在MSVC12实验的void_t版本一直有问题,还有更简单版本的void_t实现在MSVC和gcc的编译器上都不对,根据这篇演讲的说法是还需要一个提案,详见CWG 1558, treatment of unused arguments in an alias template specialization.  错误都一样,无论T是否有可用的operator=,这个元函数始终选择特化版本,即都是true_type,可能是因为void_t模板没有使用任何模板参数的原因吧,还需要进一步的研究。

这是最后的实验代码和运行结果如下:

http://ideone.com/ZUCvhY

CppCon - Modern Template Metaprogramming 杂记的更多相关文章

  1. C++模板元编程(C++ template metaprogramming)

    实验平台:Win7,VS2013 Community,GCC 4.8.3(在线版) 所谓元编程就是编写直接生成或操纵程序的程序,C++ 模板给 C++ 语言提供了元编程的能力,模板使 C++ 编程变得 ...

  2. Template Metaprogramming in C++

    说实话,学习C++以来,第一次听说"Metaprogramming"这个名词. Predict the output of following C++ program. 1 #in ...

  3. 模板元编程(Template metaprogramming)

    https://en.wikipedia.org/wiki/Template_metaprogramming 没看懂...只知道了模板元编程的代码是在编译期运行的... 敲了2个例子: 1. #inc ...

  4. C++Template 模版的本质

    我想知道上帝的構思,其他的都祇是細節.                                                                                  ...

  5. Effective C++ -----条款48:认识template元编程

    Template metaprogramming(TMP,模板元编程)可将工作由运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率. TMP可被用来生成“基于政策选择组合”(based on ...

  6. 【48】认识template元编程

    1.TMP(template metaprogramming),模版元编程有两个效力:第一,它让某些事情更容易:第二,可将工作从运行期转移到编译期.

  7. 《Effective C++》:条款48:理解力template 元编程

    Template metaprogramming(TMP,模板元编程)这是写template-based C++规划.编译过程.template metaprogramming随着C++写模板程序,化 ...

  8. Metaprogramming

    Metaprogramming https://en.wikipedia.org/wiki/Metaprogramming 元编程, 是一种编程技术, 制造的计算机程序,具有这种能力, 对待程序为他们 ...

  9. C++ template —— 表达式模板(十)

    表达式模板解决的问题是:对于一个数值数组类,它需要为基于整个数组对象的数值操作提供支持,如对数组求和或放大: Array<), y(); ... x = 1.2 * x + x * y; 对效率 ...

随机推荐

  1. linux查看压缩包的文件列表

    网上看到了一篇文章: Using bzip2 with less 这篇文章介绍了一个脚本,脚本功能就是列出压缩包所压缩的文件,本文算是原文搬运,不过减少点东西以适用我日常系统运用. #!/bin/ba ...

  2. [Search Engine] Compression in Inverted Index

    最近在学一些搜索引擎的内容,感觉挺费劲,所以就用博客当做自己的笔记,遇到一些需要整理的部分,就在这里整理一下. 今天的内容是对inverted index进行压缩.核心思想,用我自己的话来总结,就是“ ...

  3. shell编程-1到100的求和与冒泡排序

    Shell编程 一.  for循环 生成列表 {起始数..结束数} 命令生成列表 `seq [起始数] [步进长度] 结束数 ` for  l in {1..5};do for  l in `seq ...

  4. NAS4Free 安装配置(六)配置transmission实现BT(PT)下载

    配置transmission transmission是一个跨平台的BT客户端 首先我们建立一个存放transmission配置文件的目录 可以通过SSH,也可以通过网页来完成 注意:最好是通过SSH ...

  5. Android-1

    @String 支持多语言 layout中的text文本中String都尽量定义在String.xml中,便于多语言管理. <resources> <string name=&quo ...

  6. C#代码计时

    using System.Diagnostics; Stopwatch sw = new Stopwatch(); sw.Start(); //todo code ....... sw.Stop(); ...

  7. Effective Java2读书笔记-类和接口(四)

    第19条:接口只用于定义类型 这一条就举了一个反例,说有些接口中只包含常量.这是对接口的不良使用.要实现相同的功能,应该使用不可实例化的工具类(第4条说过). public class Physica ...

  8. hdu 1853 最小费用流好题 环的问题

    Cyclic Tour Time Limit: 1000/1000 MS (Java/Others)    Memory Limit: 32768/65535 K (Java/Others) Tota ...

  9. 导入旧数据需要 使用date插件

    "@version" => "1", "@timestamp" => "2016-09-12T08:31:06.630 ...

  10. PHP初识

    1. php是一种跨平台的语言,支持几乎全部的数据库. 以前觉得PHP与MYSQL是黄金组合,对于PHP能否支持MSSQL没有过了解,PHP支持几乎全部的数据库,也支持MSSQL(5.2.X版本可以用 ...