Walter E. Brown 讲解 c++ 中的 metaprograming

今天看了 Walter E. Brown 的youtube 视频,介绍 c++ 中的 metaprogramming 受益非浅。 这里记录一下学到的内容。

我已经熟悉如何使用 enable_if , is_sameconditional 等等。但是从来没有想过的如何自己动手实现这些功能,觉得实现这些功能,一定非常非常难。 Brown 的视频启发我,鼓励我,去实现这些功能,没有想象中的那么难,只是因为我们不熟悉 (unfimiliar)模板编程。

true_typefalse_type

这两个定义十分简单。很多简单的东西,看上去显而易见,第一感觉是这个东西没啥用,后来慢慢体会到,这些东西十分有用。就像阿拉伯数字 0

struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

is_same 判断两个类是否相等

首先定义主干模板 (primary template)

template<class T, class U>
struct is_same : public false_type {
};

定义一个模板,is_same<T,U> ,默认情况下,这两个类不相同。

然后部分特例化 (partial specialization)

template<class T>
struct is_same<T,T> : public true_type {
};

完整的例子如下 。

// is_same_0.cpp
#include <iostream>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

// primary defination.
template<class T, class U>
struct is_same : public false_type {
};

// partial specialization.
template<class T>
struct is_same<T,T> : public true_type {
};


int main(int argc, char *argv[])
{
    cout << "is_same<int,int> = " << is_same<int,int>::value << endl;
    cout << "is_same<int,float> = " << is_same<int,float>::value << endl;
    cout << "is_same<int,const int> = " << is_same<int,const int>::value << endl;
    return 0;
}

输出

is_same<int,int> = 1
is_same<int,float> = 0
is_same<int,const int> = 0}

这个例子中,我们看到模板编程的一个编程模式

  1. 定义 primary template 。
  2. 然后 partial specialization 。

很多时候,我们都是遵循这个模式。 primary template 类似定义接口,表明我们的模板看起来像个什么样子。 有的时候,顺便定义默认实现。 第二步,我们部分特例化,定义和默认定义不一样的案例。

is_void 查看一个类型是否是 void

is_void<T>::value 是 true ,如果 Tvoidvoid constvoid const volatile 或者是 void const volatile

首先定义 primary template .

template<class T>
struct is_void : public false_type {
};

默认 T 不是 void

然后,我们做 partial specialization 。

// partial specialization.
template<>
struct is_void<void> : public true_type {
};
template<>
struct is_void<void const> : public true_type {
};
template<>
struct is_void<void const volatile> : public true_type {
};
template<>
struct is_void<void volatile> : public true_type {
};

这个看起来有点傻,但是可读性很好,几乎就是把我们的需求重新用 c++ 语言描述一遍。后面会有一个改进版本。

完整代码

// is_void_0.cpp
#include <iostream>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

// primary defination.
template<class T>
struct is_void : public false_type {
};

// partial specialization.
template<>
struct is_void<void> : public true_type {
};
template<>
struct is_void<void const> : public true_type {
};
template<>
struct is_void<void const volatile> : public true_type {
};
template<>
struct is_void<void volatile> : public true_type {
};


int main(int argc, char *argv[])
{
    cout << "is_void<int> = " << is_void<int>::value << endl;
    cout << "is_void<void> = " << is_void<void>::value << endl;
    cout << "is_void<void const> = " << is_void<void const>::value << endl;
    cout << "is_void<void volatile> = " << is_void<void volatile>::value << endl;
    cout << "is_void<void const volatile> = " << is_void<void const volatile>::value << endl;
    return 0;
}

程序输出

is_void<int> = 0
is_void<void> = 1
is_void<void const> = 1
is_void<void volatile> = 1
is_void<void const volatile> = 1

remove_cv 删除一个类的 const volatile 修饰

remove_cv<void>, remove_cv<void const> ,remove_cv<void volatile>remove_cv<void const volatile> 都是 void

定义 primary template

// partial specialization
template<class T>
struct remove_cv<T const> {
    using type = T;
};

定义 partial specialization

// partial specialization
template<class T>
struct remove_cv<T const> {
    using type = T;
};
template<class T>
struct remove_cv<T volatile> {
    using type = T;
};
template<class T>
struct remove_cv<T volatile const> {
    using type = T;
};

这里通常用 type 来表示应用一个 template 之后的结果。

完整代码

// remove_cv_0.cpp
#include <iostream>
#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
using std::cout;
using std::endl;

// primary defination.
template<class T>
struct remove_cv {
    using type = T;
};
// partial specialization
template<class T>
struct remove_cv<T const> {
    using type = T;
};
template<class T>
struct remove_cv<T volatile> {
    using type = T;
};
template<class T>
struct remove_cv<T volatile const> {
    using type = T;
};


int main(int argc, char *argv[])
{
    cout << "remove_cv<int> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<const int> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<int const> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<int volatile> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<int const volatile> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    return 0;
}

程序输出

remove_cv<int> = int
remove_cv<const int> = int
remove_cv<int const> = int
remove_cv<int volatile> = int
remove_cv<int const volatile> = int

重写 remove_cv

我们可以写简化一下 remove_cv ,这里的 "简化" ,我理解不是“变得更简单" 的意思。而是让他变得更加 “可组合的”。

这个比较简单,直接上代码了。这里不停地重复一个模式,就是上面谈到的模式。

完整代码

// remove_cv_0.cpp
#include <iostream>
#include <boost/type_index.hpp>
using boost::typeindex::type_id_with_cvr;
using std::cout;
using std::endl;

// primary defination.
template<class T>
struct remove_const {
    using type = T;
};
// partial specialization
template<class T>
struct remove_const<T const> {
    using type = T;
};

// primary defination
template<class T>
struct remove_volatile{
    using type = T;
};
// partial specialization
template<class T>
struct remove_volatile<T volatile> {
    using type = T;
};

// bang
template<class T>
struct remove_cv {
    using type = typename remove_const<typename remove_volatile<T>::type >::type;
};


int main(int argc, char *argv[])
{
    cout << "remove_cv<int> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<const int> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<int const> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<int volatile> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    cout << "remove_cv<int const volatile> = " << type_id_with_cvr<remove_cv<int>::type >().pretty_name() << endl;
    return 0;
}

程序输出

remove_cv<int> = int
remove_cv<const int> = int
remove_cv<int const> = int
remove_cv<int volatile> = int
remove_cv<int const volatile> = int

重写 is_void

有了 is_sameremove_cv ,我们可以简化 is_void 的实现。

// primary defination.
template<class T>
struct is_void : public is_same<typename remove_cv<T>::type, void> {
};

完整代码

// is_void_0.cpp
#include <iostream>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

// primary defination.
template<class T, class U>
struct is_same : public false_type {
};

// partial specialization.
template<class T>
struct is_same<T,T> : public true_type {
};
// primary defination.
template<class T>
struct remove_cv {
    using type = T;
};
// partial specialization
template<class T>
struct remove_cv<T const> {
    using type = T;
};
template<class T>
struct remove_cv<T volatile> {
    using type = T;
};
template<class T>
struct remove_cv<T volatile const> {
    using type = T;
};


// primary defination.
template<class T>
struct is_void : public is_same<typename remove_cv<T>::type, void> {
};



int main(int argc, char *argv[])
{
    cout << "is_void<int> = " << is_void<int>::value << endl;
    cout << "is_void<void> = " << is_void<void>::value << endl;
    cout << "is_void<void const> = " << is_void<void const>::value << endl;
    cout << "is_void<void volatile> = " << is_void<void volatile>::value << endl;
    cout << "is_void<void const volatile> = " << is_void<void const volatile>::value << endl;
    return 0;
}

程序输出

is_void<int> = 0
is_void<void> = 1
is_void<void const> = 1
is_void<void volatile> = 1
is_void<void const volatile> = 1

is_one_of 判断一个类是否是某些类中的一个

  1. is_one_of<int>::valuefalse
  2. is_one_of<int,int>::valuetrue
  3. is_one_of<int, float, int>::valuetrue
  4. is_one_of<int, float, double>::valuefalse
  5. is_one_of<int, float, double, int>::valuetrue

这里使用了 c++11 中的可变长模板的特性,但是基本的模板编程模式是不变的。

定义 primary template

// primary defination.
template<class T, class... P0toN>
struct is_one_of : public false_type {
};

定义 partial specialization

// partial specialization.
template<class T, class... P1toN>
struct is_one_of<T,T, P1toN...> : public true_type {
};

这个是显而易见的,如果和列表中的第一个类型相同,那么就是 true_type

如果匹配失败呢,那么我们有一种递归消减的模式。

template<class T, class U, class... P1toN>
struct is_one_of<T, U, P1toN...> : public is_one_of<T, P1toN...> {
};

或者

template<class T, class U, class... P1toN>
struct is_one_of<T, U, P1toN...> {
    static constexpr bool value = is_one_of<T, P1toN...>::value;
};

我不确定哪一种风格是好的。

完整代码

// is_one_of_0.cpp
#include <iostream>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

// primary defination.
template<class T, class... P0toN>
struct is_one_of : public false_type {
};

// partial specialization.
template<class T, class... P1toN>
struct is_one_of<T,T, P1toN...> : public true_type {
};
template<class T, class U, class... P1toN>
struct is_one_of<T, U, P1toN...> : public is_one_of<T, P1toN...> {
};


int main(int argc, char *argv[])
{
    cout << "is_one_of<int> = " << is_one_of<int>::value << endl;
    cout << "is_one_of<int,int> = " << is_one_of<int,int>::value << endl;
    cout << "is_one_of<int,float,int> = " << is_one_of<int,float,int>::value << endl;
    cout << "is_one_of<int,float,double> = " << is_one_of<int,float,double>::value << endl;

    return 0;
}

程序输出

is_one_of<int> = 0
is_one_of<int,int> = 1
is_one_of<int,float,int> = 1
is_one_of<int,float,double> = 0

is_copy_assignable 判断一个类是否可复制,可赋值

如果 T 定义了拷贝构造函数和赋值操作符重载,那么 is_copy_assignable<T>::valuetrue ,否则就是 false 。举几个 false 的例子

  1. is_copy_assignable<unique_ptr<int> >::value
  2. is_copy_assignable<mutex >::value
  3. is_copy_assignable<lock_guard<mutex> >::value

这个实现起来是很有难度的。我们从简单的事情做起。第一步,写 primary template 。

template<typename T>
struct is_copy_assignable : true_type {
}

恩,这个看起来很简单,不是吗?实例化的部分就有难度了。

首先,我们定义一个简单的东西,declval

template<typename T>
T declval();

声明一个模板函数,declval<T>() 的返回值的类型是 T 。这个函数没有定义,我们无法运行期调用这个函数。

编译期,我们可以使用 decltype(declval<T>()) 得到类型 Tdecltype 是 c++11 的特性。

如果一个类定义了赋值操作符重载,那么下面的表达式就是成立的。

decltype( declval<U&>() = declval<U const&>() )

declval<U const&>() 返回一个 U const& 的对象 xdeclval<U&>() 返回一个 U& 的对象 y ,然后试图调用赋值语句,y=x

等等,这里我们并没有真正的执行求值(evaluate) 的动作,无论是编译期还是运行期。decltype 就像 sizeof, noexcepttypeid 一样,并不真正执行求值的动作。一切发生在想象中。

无论如何,如果 U 没有定义 = 操作符重载的话,上面的语句就会失败(failure) ,注意,我没有使用出错(error) 这个表达方式。 SFINAE (Substitue Failure Is Not An Error) 。就是说,如果上面的表达式是不合法的 (ill-formed) ,那么编译器不认为是错误,继续尝试匹配其他的表达式。

template<typename 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>()));
    static constexpr bool value = type::value;
};

通常,起名字是程序员最头痛的事情,如果你不引用一个东西,就不要给他起名字。例如上面的例子,class = ... 就没有起名字,尽管没有名字,这个模板参数可以有一个默认值。默认值就是那一长串 decltype( declval<U&>() = declval<U const&>()

static false_type try_assignment(...);

这个是一个十分不常用的语法,... ,来自于 C 语言的历史遗产,printf(...) 。 这个在 C++ 中极力不提倡使用,推荐使用可变长模板。原因是这个没有 type safe ,多少 c 语言的 bug 倒在这个上面。 这 C++ 中,可变长的参数的函数,在函数重载(overload) 中是最后一个选项。也就谁说,函数重载 (overload) 的时候,寻找合适的重载函数时候,只有所有其他匹配都不成功的时候,才会匹配这个可变长参数的同名函数。

把两个重载的 try_assigment 连起来看,我们理解一下 SFINAE 。如果 U 定义了 = 操作符重载,那么 decltype( declval<U&>() = declval<U const&>() 就是一个合理的表达式,匹配成功,于是 try_assigment() 的返回值的类型是 true_type 。否则,匹配失败,但是失败不是错误,继续匹配,匹配到了第二个可变长参数及的 try_assigment,这时,返回值的类型是 false_type

如果我们在重复利用 delctypedeclval 的技巧,就可以得到 type 的定义。

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

后面的 static constexpr bool value 就不难理解了。

这段代码极其不好理解,因为看起来十分的奇怪。这也是为什么 template 让大家觉得陌生。

这段代码并不是最好的,因为用到太多的小技巧,后面 Walter E. Brown 会试图重写一下。

有几个技术是值得学习的。 decltype ,这个十分有用。decltype 很独特,因为他的参数是一个表达式,而不是一个 type ,这个表达式即不在运行期求值,也不在编译器求值。 为了配合使用 decltype ,我们才会引入一个 declval<T>() 的傻函数。 decltype + declval 的技术不是很难,十分有用,应该被掌握。

... 的可变参数,还有函数重载的复杂规则,我们应该敬而远之。尤其是函数重载。考虑到默认构造函数,默认类型转换函数,默认参数,模板函数等等语言特性,模板函数的重载规则是十分复杂的。如果大多数人都记不住这些复杂的规则,那么利用这些规则写出来的代码,就可读性很差了。

这些例子远远没有到达库函数的质量,因为我们没有考虑很多边缘案例,例如,如果 T 有等号操作符重载,但是等号操作符重载函数的返回值不是 T&

完整代码

// is_copy_assignable_0.cpp
#include <iostream>
#include <memory>
#include <mutex>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

template<typename T>
T declval();


// primary defination.
template<typename 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>()));
    static constexpr bool value = type::value;
};


int main(int argc, char *argv[])
{
    cout << "is_copy_assignable<unique_ptr<int> > = " <<
            is_copy_assignable<std::unique_ptr<int> >::value << endl;

    cout << "is_copy_assignable<mutex> = " <<
            is_copy_assignable<std::mutex>::value << endl;


    cout << "is_copy_assignable<lock_guard> = " <<
            is_copy_assignable<std::lock_guard<std::mutex> >::value << endl;

    cout << "is_copy_assignable<int > = " <<
            is_copy_assignable<int>::value << endl;

    return 0;
}

程序输出

is_copy_assignable<unique_ptr<int> > = 0
is_copy_assignable<mutex> = 0
is_copy_assignable<lock_guard> = 0
is_copy_assignable<int > = 1

void_t 一个奇怪的,而有用的模板类

template<class...>
using void_t = void;

void_t 已经是 c++17 的标准了 http://en.cppreference.com/w/cpp/types/void_t 。 但是大神讲这个视频的时候,还没有进去。

这个类型看起来没啥用,很简单。

你给他无论啥类型,他都返回一个 void 类型。

关键点是,你给他的类型必须是有效的类型 (well-formed),不能是非法的的类型(ill-formed)。

这里还用的了 c++11 的一个新特性, using ,这个十分有用,让代码看起来十分简洁。

后面大神讲到应用这个 void_t 重写 is_copy_assignable 的时候,现场一片掌声。

使用 void_t,编写 has_type_member

如果 T::type 是一个合法的表达式,那么 has_type_member<T>::value 是 true ,否则就是 false 。例如

  1. has_type_member<enable_if<true> >::value = true
  2. has_type_member<int>::value = false

按照模式,我们首先定义 primary template

// primary defination.
template<class T, class = void >
struct has_type_member : public true_type {
};

很明显,大多数我们自定义的类,都没有 T::type 的定义,于是默认返回 false 。

注意 class = void 十分关键。 要理解他为什么十分关键,就需要理解 c++ 是如何找到匹配的 template specialization 的。这个比较长,我简单说一下我的理解。

  1. 首先确定模板参数。
  2. 看用户给定的参数,如果有给定参数,那么用用户提供的参数。
  3. 如果用户没有提供模板参数,那么用默认的。
  4. 找到所有模板参数之后,看看哪一个实例化是更加匹配的,找到最匹配的那一个。

对于 has_type_member<int>来说,has_type_member 的第一个模板参数是 int ,用户提供的,第二个模板参数,用户没有提供,我们用默认的,就是 void ,这就是为什么 class = void` 十分关键。

找到参数之后,这个时候,编译器发现有两个匹配的模板。

部分特例化的 struct has_type_member<T, void_t<typename T::type> > : public true_type ,这个匹配不上,因为 void_t<int::type> 匹配失败。 匹配失败不是错误,继续匹配。匹配到了 primary template 。于是 has_type_member<int>::value 是 false 。

对于 has_type_member<enable_if<true,void> >来说,has_type_member 的第一个模板参数是 enable_if<true,void> ,用户提供的,第二个模板参数,用户没有提供,我们用默认的,就是 void 。

部分特例化的 struct has_type_member<T, void_t<typename T::type> > : public true_type ,这个匹配成功,因为 void_t<int::type> 是合理的表达式。于是 has_type_member<T, void_t<typename T::type>::value 是 true 。

这个模式十分有用,简单易懂。可以判断一个类是否有成员函数,静态成员变量,成员变量等等。我们就离实现 concept 不远了。

完整代码

// has_type_member_0.cpp
#include <iostream>
#include <type_traits>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

// the imfamous void_t
template<class...>
using void_t = void;


// primary defination.
template<class T, class = void >
struct has_type_member : public false_type {
};

// partial specialization.
template<class T>
struct has_type_member<T, void_t<typename T::type> > : public true_type {
};


int main(int argc, char *argv[])
{
    cout << "has_type_member<int> = " << has_type_member<int>::value << endl;
    cout << "has_type_member<std::enable_if<true,void> > = " << has_type_member<std::enable_if<true,void> >::value << endl;
    return 0;
}

程序输出

has_type_member<int> = 0
has_type_member<std::enable_if<true,void> > = 1

重写 is_copy_assignable

应用模式,写出 primary template

// primary defination.
template<typename T, class = void>
struct is_copy_assignable : public false_type {
};

默认都不能 copy assignable 。

然后 partial specialization

// partial specialization
template<typename T>
struct is_copy_assignable<T, void_t<decltype( declval<T&>() = declval<T const&>() )> >
        : public true_type {
};

这里 Brown 赢得的一片掌声。 的确精妙。 他的精妙之处,不在于解决了一个特定的问题,而在于他创造了一种模式,应用这种模式,让人们不再使用那些难懂的技巧,就可以写出可读性很好的模板程序。

完整代码

// is_copy_assignable_1.cpp
#include <iostream>
#include <memory>
#include <mutex>
using std::cout;
using std::endl;


struct true_type {
    static constexpr bool value = true;
};
struct false_type {
    static constexpr bool value = false;
};

template<typename T>
T declval();

// the imfamous void_t
template<class...>
using void_t = void;


// primary defination.
template<typename T, class = void>
struct is_copy_assignable : public false_type {
};

// partial specialization
template<typename T>
struct is_copy_assignable<T, void_t<decltype( declval<T&>() = declval<T const&>() )> >
        : public true_type {
};



int main(int argc, char *argv[])
{
    cout << "is_copy_assignable<unique_ptr<int> > = " <<
            is_copy_assignable<std::unique_ptr<int> >::value << endl;

    cout << "is_copy_assignable<mutex> = " <<
            is_copy_assignable<std::mutex>::value << endl;


    cout << "is_copy_assignable<lock_guard> = " <<
            is_copy_assignable<std::lock_guard<std::mutex> >::value << endl;

    cout << "is_copy_assignable<int > = " <<
            is_copy_assignable<int>::value << endl;

    return 0;
}

程序输出

is_copy_assignable<unique_ptr<int> > = 0
is_copy_assignable<mutex> = 0
is_copy_assignable<lock_guard> = 0
is_copy_assignable<int > = 1