Modern Template Metaprogramming - A Compendium - Walter E Brown - CppCon 2014.pdf

https://en.cppreference.com/w/cpp/header/type_traits

C++模板元编程学习

  1. 模板元编程中,structclass不再是一个对数据和方法的封装类,而是变成了一个某种意义上的“函数对象”。<br /> 这个“函数对象”中,通过模板参数表示函数入参,通过一个特殊的 type 符号表示计算结果,而函数运算本身,则通过编译时表达式赋给type。<br /> 在这个特殊的“函数对象”中:
  • 可以接受的参数、可以执行的运算是受限的,参数只能是编译期已知的整形类型,运算也只能是编译期可用的表达式。
  • 用有限的表达式,可以实现非常方便的功能,但其实现方式非常讨巧,在习惯了OO的程序员眼中都是“黑科技”,因此必须要花些时间学习和熟悉这种编程方式。

    1. 目前C++模板的类型参数不支持像java那样的接口定义,类型参数是否合法完全根据“duck-type”规则来检查,这使得模板定义写起来方便,但既不易于维护,也不易于理解。未来C++有望引入concept概念,来约束模板类型参数。不过,直到C++17concept概念依然流产了。C++20concept终于来了!

避免自己写template metaprogram

模板元编程技巧非常诡异,很多C++开发者都尽可能远离它。自己编写的模板元代码难以理解和维护。应该尽可能使用标准库提供的现成模板。

模板元编程范式

  • 模板元编程本质是函数式编程,每一个struct/class都定义了一个函数,并且每一个struct/class都可以被当作参数传入另一个struct/class模板的参数。
  • 存在一些只有模板元编程可以做的事情,比如在编译期判断一个对象的类型是否继承自某个父类,这些事情都依赖于模板的特化匹配规则(模式匹配)和SFINAE规则。

    概念要结合实践,下面是几个常见的模板元编程范式:

  1. 定义一个“模板函数”BoolType,用于将true/false转换为唯一对应的类型:

    template struct BoolType;
    template <>
    struct BoolType {
    enum { Value = true };
    using Result = BoolType;
    };
    template <>
    struct BoolType {
    enum { Value = false };
    using Result = BoolType;
    };
    类似的,也可以用相似的方式定义将int转为唯一类型的模板函数:
    template struct IntType {
    enum { Value = V };
    using Result = IntType;
    }
    一般都用struct来定义模板函数,因为这样就不用写public了。

  2. 定义isEqual,用于判断两个类型是否相同,用到了模板的偏特化规则:

    template
    struct isEqual {
    using Result = FalseType; // FalseType是BoolType的别名
    };
    // 下面是isEqual的偏特化定义,注意isEqual后面跟着的模板参数
    template
    struct isEqual {
    using Result = TrueType;
    };

  3. 定义isConvertible,判断类型T是否可以默认转型(或者说赋值)给类型U,用到了可变模板参数及函数重载规则,并且利用self()函数在不知道T类型细节的情况下施行重载决议:

    template
    struct isConvertible {
    using Yes = char;
    struct No { char dummy[2]; };

    static Yes test(U);
    static No Test(…);
    static T self();

    using Result = BoolType< sizeof(Yes) == sizeof(test(self())) >;
    };
    利用isConvertible,也可以定义出判断T是否是U的父类的模板方法,提示:const U可以向const T转型,但是const T不是const void,同时const T和const U不是相同类型。

  4. C++11中支持了变长模板参数,不仅可以定义出参数变长的函数,也可以定义出参数变长的模板类,后者可以用于定义模板元编程中的变长函数,写法和模板函数的递归类似:

    template struct Sum;
    template
    struct Sum {
    using Result = Add< Number, Sum::Result>::Result;
    };
    template <>
    struct Sum<> {
    using Result = IntType<0>;
    };

  5. 使用模板在编译期根据模板参数选取类的实现:

    template
    struct ClonableCreator {
    static T create(const T instance) {
    return instance->clone();
    }
    };
    template
    struct UnclonableCreator {
    static T create(const T instance) {
    return new T(*instance);
    }
    };
    template
    using Creator = if(bool(isClonable), ClonableCreator, UnclonableCreator);
    如果直接定义Creator类并将if写在Creator函数体内的话,在isClonable为false时,编译器还是会由于T没有定义clone方法而报错,利用模板元编程方式则在运行时生成代码,巧妙避开了这一问题。