美文网首页C++ Templates
【C++ Templates(17)】Traits的实现(下)

【C++ Templates(17)】Traits的实现(下)

作者: downdemo | 来源:发表于2018-06-13 10:00 被阅读21次

    07 其他traits技术

    • 最后介绍一些其他定义traits的方法

    7.1 If-Then-Else

    • 之前的PlusResultT traits最终的定义有一个完全不同的实现,它依赖于另一个type traits,HasPlusT的结果。可以用特殊的类型模板IfThenElse构建这个行为,它带有一个布尔非类型模板参数来选择两种类型参数之一
    // traits/ifthenelse.hpp
    
    #ifndef IFTHENELSE_HPP
    #define IFTHENELSE_HPP
    
    // primary template: yield the second argument by default and rely on
    // a partial specialization to yield the third argument if COND is false
    template<bool COND, typename TrueType, typename FalseType>
    struct IfThenElseT {
        using Type = TrueType;
    };
    
    // partial specialization: false yields third argument
    template<typename TrueType, typename FalseType>
    struct IfThenElseT<false, TrueType, FalseType> {
        using Type = FalseType;
    };
    
    template<bool COND, typename TrueType, typename FalseType>
    using IfThenElse =
        typename IfThenElseT<COND, TrueType, FalseType>::Type;
    
    #endif // IFTHENELSE_HPP
    
    • 下面的例子演示了一个此模板的应用,它定义了一个类型函数来对一个给定值确定最低级别的整型
    // traits/smallestint.hpp
    
    #include <limits>
    #include "ifthenelse.hpp"
    
    template<auto N>
    struct SmallestIntT {
        using Type =
            typename IfThenElseT<N <= std::numeric_limits<char> ::max(), char,
                typename IfThenElseT<N <= std::numeric_limits<short> ::max(), short,
                    typename IfThenElseT<N <= std::numeric_limits<int> ::max(), int,
                        typename IfThenElseT<N <= std::numeric_limits<long>::max(), long,
                            typename IfThenElseT<N <= std::numeric_limits<long long>::max(),
                            long long, // then
                            void // fallback
                        >::Type
                    >::Type
                >::Type
            >::Type
        >::Type;
    };
    
    • 注意,不同于常规的if-then-else语句,这里then和else分支的模板实参在被选择前都会被估算,所以不能有非法的代码。比如一个traits对给定的带符号类型产生对应的无符号类型,有一个标准traits,std::make_unsigned,但它要求传递类型是带符号整型且不为bool,否则产生ub,下面这个天真的实现就无法工作
    // ERROR: undefined behavior if T is bool or no integral type:
    template<typename T>
    struct UnsignedT {
        using Type = IfThenElse<std::is_integral<T>::value
                && !std::is_same<T,bool>::value,
            typename std::make_unsigned<T>::type,
            T>;
    };
    
    • 实例化UnsignedT<bool>仍是ub,因为编译器仍会尝试从下式构建类型
    typename std::make_unsigned<T>::type
    
    • 为了指出这个问题,需要添加一个间接级别,使得IfThenElse实参是本身对包裹结果的类型函数的使用
    // yield T when using member Type:
    template<typename T>
    struct IdentityT {
        using Type = T;
    };
    
    // to make unsigned after IfThenElse was evaluated:
    template<typename T>
    struct MakeUnsignedT {
        using Type = typename std::make_unsigned<T>::type;
    };
    
    template<typename T>
    struct UnsignedT {
        using Type = typename IfThenElse<std::is_integral<T>::value
            && !std::is_same<T,bool>::value,
            MakeUnsignedT<T>,
            IdentityT<T>
            >::Type;
    };
    
    • 在UnsignedT定义中,IfThenElse的类型实参都是类型函数本身的实例化。然而类型函数不是在IfThenElse选择一个之前实例化,而是选择类型函数实例(MakeUnsignedT或IdentityT的实例)前,::Type再估计选择的类型函数实例来产生Type。必须强调的是,这完全依赖于IfThenElse中未选择的包裹类型从不完全实例化的事实,特别地,下面代码不能工作
    template<typename T>
    struct UnsignedT {
        using Type = typename IfThenElse<std::is_integral<T>::value
            && !std::is_same<T,bool>::value,
            MakeUnsignedT<T>::Type,
            T
            >::Type;
    };
    
    • 必须之后使用MakeUnsignedT<T>的::Type,这意味着需要IdentityT辅助也要在else分支中之后使用T的::Type,因此不能使用如下
    template<typename T>
    using Identity = typename IdentityT<T>::Type;
    
    • 可以声明这样的别名模板,它可能在其他地方有用,但不能将它有效利用于IfThenElse的定义,因为Identity<T>的使用会立刻造成Identity<T>的实例化来取回它的Type成员
    • IfThenElse模板在标准库中是可用的,为std::conditional<>,用它可以如下定义UnsignedT
    template<typename T>
    struct UnsignedT {
        using Type
            = typename std::conditional_t<std::is_integral<T>::value
            && !std::is_same<T,bool>::value,
            MakeUnsignedT<T>,
            IdentityT<T>
            >::Type;
    };
    

    7.2 检查不抛出异常的操作

    • 确定一个特殊的操作能否抛出异常有时是有用的。比如,一个移动构造函数应该被标记为noexcept,然而这通常依赖于它的成员的基类是否抛异常。比如下面是一个简单类模板Pair的移动构造函数
    template<typename T1, typename T2>
    class Pair {
        T1 first;
        T2 second;
    public:
        Pair(Pair&& other)
        : first(std::forward<T1>(other.first)),
        second(std::forward<T2>(other.second)) {
        }
    };
    
    • 剩下要做的就是实现IsNothrowMoveConstructibleT traits,可以用noexcept操作符来保证表达式不抛异常直接实现
    // traits/isnothrowmoveconstructible1.hpp
    
    #include <utility> // for declval
    #include <type_traits> // for bool_constant
    
    template<typename T>
    struct IsNothrowMoveConstructibleT
    : std::bool_constant<noexcept(T(std::declval<T>()))>
    {
    };
    
    • 因为noexcept结果是一个布尔值,可以直接传递定义基类std::bool_constant<>,它用于定义std::true_type和std::false_type。然而这个实现需要改进,因为它不是SFINAE-friendly:如果traits对一个没有有用的移动或构造函数的类型初始化,使得T(std::declval<T&&>())无效,则整个程序非法
    class E {
    public:
        E(E&&) = delete;
    };
    ...
    std::cout << IsNothrowMoveConstructibleT<E>::value; // 编译期错误
    
    • 这里traits应该产生一个false值而不是报错。这里必须在检查移动构造函数是否为noexcept前确定它是否有效,因此修改第一个版本的traits,添加一个默认为void的模板参数和一个使用std::void_t参数的局部特化,仅当构造函数有效时后者的实参有效
    // traits/isnothrowmoveconstructible2.hpp
    
    #include <utility> // for declval
    #include <type_traits> // for true_type, false_type, and bool_constant<>
    
    // primary template:
    template<typename T, typename = std::void_t<>>
    struct IsNothrowMoveConstructibleT : std::false_type
    {
    };
    
    // partial specialization (may be SFINAE'd away):
    template<typename T>
    struct IsNothrowMoveConstructibleT
    <T, std::void_t<decltype(T(std::declval<T>()))>>
    : std::bool_constant<noexcept(T(std::declval<T>()))>
    {
    };
    
    • 如果局部特化中std::void_t<...>的替换有效,特化被选择,否则被丢弃不被实例化,而是实例化基本模板,产生一个std::false_type结果
    • 注意,如果不能直接访问移动构造函数则无法检查它是否抛异常,移动构造函数是public且不是deleted是不够的,还要对应的类型不是抽象类(但抽象类的引用或指针可以),因此traits名为IsNothrowMoveConstructible而不是HasNothrowMove-Constructor
    • 标准库提供了对应的type traits std::is_move_constructible<>

    7.3 traits的便利性

    • 一个关于traits常见的抱怨就是冗长,每次使用type traits都要一个尾置的::Type,一个typename关键词,多个type traits组合时将使得格式十分笨拙,比如对数组operator+,正确实现保证不返回常量或引用
    template<typename T1, typename T2>
    Array<
        typename RemoveCVT<
            typename RemoveReferenceT<
                typename PlusResultT<T1, T2>::Type
            >::Type
        >::Type
    >
    operator+ (Array<T1> const&, Array<T2> const&);
    
    • 通过使用别名模板和变量模板,使用这个traits,可以更方便地产生对应类型或值,但注意一些context中不能使用这些简写,必须使用原始的类模板

    7.3.1 别名模板和traits

    • 别名模板可以简化代码,比如下面三个别名模板包裹了上述的type traits
    template<typename T>
    using RemoveCV = typename RemoveCVT<T>::Type;
    
    template<typename T>
    using RemoveReference = typename RemoveReferenceT<T>::Type;
    
    template<typename T1, typename T2>
    using PlusResult = typename PlusResultT<T1, T2>::Type;
    
    • 给出这些别名模板,可以简化operator+声明
    Click here to view code image
    template<typename T1, typename T2>
    Array<RemoveCV<RemoveReference<PlusResultT<T1, T2>>>>
    operator+ (Array<T1> const&, Array<T2> const&);
    
    • 然而使用别名模板也有一些缺点
      • 别名模板不能被特化,traits的许多技术依赖于特化,此时只能把别名模板改回类模板
      • 一些traits有意让用户特化,比如描述一个特殊的附加操作是否可替换,当大多数使用涉及别名模板时,特化类模板就会造成迷惑
      • 使用别名模板总会实例化类型,将使得对给定类型难以避免无意义的实例化traits。换句话说,别名模板不能用于元函数转发

    7.3.2 变量模板和traits

    • traits返回一个值需要一个尾置的::value来产生结果,这种情况下constexpr变量模板可以简化代码。下面的变量模板包裹了之前的IsSameT和IsConvertibleT
    template<typename T1, typename T2>
    constexpr bool IsSame = IsSameT<T1,T2>::value;
    template<typename FROM, typename TO>
    constexpr bool IsConvertible = IsConvertibleT<FROM, TO>::value;
    
    • 现在可以简化
    if (IsSameT<T,int>::value || IsConvertibleT<T,char>::value) ...
    // 简写为
    if (IsSame<T,int> || IsConvertible<T,char>) ...
    

    08 类型分类(Type Classification)

    • 有时需要知道模板参数是内置类型、指针类型还是类类型等,下面将开发一系列用于确定一个给定类型的各种属性的traits。结果将可以对一些类型写出
    if (IsClassT<T>::value) {
        ...
    }
    
    • 或使用C++17的编译期if
    if constexpr (IsClass<T>) {
        ...
    }
    
    • 或使用局部特化
    class C { // primary template for the general case
        ...
    };
    template<typename T>
    class C<T, true> { // partial specialization for class types
        ...
    };
    

    8.1 判断基本类型

    • 先开发一个确定基本类型的模板,默认假设一个类型不是基本类型,并对基本类型的情况特化
    // traits/isfunda.hpp
    
    #include <cstddef> // for nullptr_t
    #include <type_traits> // for true_type, false_type, and bool_constant<>
    
    // primary template: in general T is not a fundamental type
    template<typename T>
    struct IsFundaT : std::false_type {
    };
    
    // macro to specialize for fundamental types
    #define MK_FUNDA_TYPE(T) \
        template<> struct IsFundaT<T> : std::true_type { \
        };
    
    MK_FUNDA_TYPE(void)
    
    MK_FUNDA_TYPE(bool)
    MK_FUNDA_TYPE(char)
    MK_FUNDA_TYPE(signed char)
    MK_FUNDA_TYPE(unsigned char)
    MK_FUNDA_TYPE(wchar_t)
    MK_FUNDA_TYPE(char16_t)
    MK_FUNDA_TYPE(char32_t)
    
    MK_FUNDA_TYPE(signed short)
    MK_FUNDA_TYPE(unsigned short)
    MK_FUNDA_TYPE(signed int)
    MK_FUNDA_TYPE(unsigned int)
    MK_FUNDA_TYPE(signed long)
    MK_FUNDA_TYPE(unsigned long)
    MK_FUNDA_TYPE(signed long long)
    MK_FUNDA_TYPE(unsigned long long)
    
    MK_FUNDA_TYPE(float)
    MK_FUNDA_TYPE(double)
    MK_FUNDA_TYPE(long double)
    
    MK_FUNDA_TYPE(std::nullptr_t)
    
    #undef MK_FUNDA_TYPE
    
    • 基本模板定义通用情况,IsFundaT<T>::value为false,对每个基本类型定义一个特化,IsFundaT<T>::value为true,为了方便定义了一个宏扩展代码
    MK_FUNDA_TYPE(bool)
    // 扩展为
    template<> struct IsFundaT<bool> : std::true_type {
        static constexpr bool value = true;
    };
    
    • 下面是一种可能的使用
    // traits/isfundatest.cpp
    
    #include "isfunda.hpp"
    #include <iostream>
    
    template<typename T>
    void test (T const&)
    {
        if (IsFundaT<T>::value) {
            std::cout << "T is a fundamental type" << '\n';
        }
        else {
            std::cout << "T is not a fundamental type" << '\n';
        }
    }
    
    int main()
    {
        test(7); // T is a fundamental type
        test("hello"); // T is not a fundamental type
    }
    

    8.2 判断复合类型

    • 简单复合类型包括指针类型、左值和右值引用类型、pointer-to-member类型、数组类型。类类型和函数类型也是复合类型,但他们的组合能涉及任意数量的类型。枚举类型在这里被认为是非简单复合类型。简单复合类型可以用局部特化分类
    • 指针。标准库提供了std::is_pointer<>
    // traits/ispointer.hpp
    
    template<typename T>
    struct IsPointerT : std::false_type { // primary template: by default not a pointer
    };
    template<typename T>
    struct IsPointerT<T*> : std::true_type { // partial specialization for pointers
        using BaseT = T; // type pointing to
    };
    
    • 引用。标准库提供了std::is_lvalue_reference<>,std::is_rvalue_reference<>以及std::is_reference<>
    // traits/islvaluereference.hpp
    
    template<typename T>
    struct IsLValueReferenceT : std::false_type { // by default no lvalue reference
    };
    template<typename T>
    struct IsLValueReferenceT<T&> : std::true_type { // unless T is lvalue references
        using BaseT = T; // type referring to
    };
    
    // traits/isrvaluereference.hpp
    
    template<typename T>
    struct IsRValueReferenceT : std::false_type { // by default no rvalue reference
    };
    template<typename T>
    struct IsRValueReferenceT<T&&> : std::true_type { // unless T is rvalue reference
        using BaseT = T; // type referring to
    };
    
    // traits/isreference.hpp
    
    #include "islvaluereference.hpp"
    #include "isrvaluereference.hpp"
    #include "ifthenelse.hpp"
    
    template<typename T>
    class IsReferenceT
    : public IfThenElseT<IsLValueReferenceT<T>::value,
        IsLValueReferenceT<T>,
        IsRValueReferenceT<T>
        >::Type {
    };
    
    • 数组。标准库提供了std::is_array<>,同时提供了std::rank<>和std::extent<>来允许查询大小
    // traits/isarray.hpp
    
    #include <cstddef>
    template<typename T>
    struct IsArrayT : std::false_type { // primary template: not an array
    };
    
    template<typename T, std::size_t N>
    struct IsArrayT<T[N]> : std::true_type { // partial specialization for arrays
        using BaseT = T;
        static constexpr std::size_t size = N;
    };
    
    template<typename T>
    struct IsArrayT<T[]> : std::true_type { // partial specialization for unbound arrays
        using BaseT = T;
        static constexpr std::size_t size = 0;
    };
    
    • pointer-to-member。标准库提供了std::is_member_object_pointer<>,std::is_member_function_pointer<>以及std::is_member_pointer<>
    // traits/ispointertomember.hpp
    
    template<typename T>
    struct IsPointerToMemberT : std::false_type { // by default no pointer-to-member
    };
    
    template<typename T, typename C>
    struct IsPointerToMemberT<T C::*> : std::true_type { // partial specialization
        using MemberT = T;
        using ClassT = C;
    };
    

    8.3 判断函数类型

    • 函数类型有任意数量的参数影响结果,因此在匹配函数类型的局部特化中,借用一个参数包来捕获所有的参数类型,就像之前对DecayT traits做的
    // traits/isfunction.hpp
    
    #include "../typelist/typelist.hpp"
    
    template<typename T>
    struct IsFunctionT : std::false_type { // primary template: no function
    };
    
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params...)> : std::true_type
    { //functions
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = false;
    };
    
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params..., ...)> : std::true_type { // variadic functions
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = true;
    };
    
    • 不幸的是,IsFunctionT的构建不能处理所有函数类型,因为函数类型可以带cv限定符、左值右值引用限定符,以及C++17的noexcept限定符,比如
    using MyFuncType = void (int&) const;
    
    • 这样的函数类型只能用于nonstatic成员函数。此外标记为const的函数类型不是真的const类型,RemoveConst无法去除const。因此为了识别带限定符的函数类型,需要引入大量的附加的局部特化来覆盖所有的限定符组合。这里只阐述其中五个
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params...) const> : std::true_type {
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = false;
    };
    
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params..., ...) volatile> : std::true_type {
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = true;
    };
    
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params..., ...) const volatile> :
    std::true_type {
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = true;
    };
    
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params..., ...) &> : std::true_type {
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = true;
    };
    
    template<typename R, typename... Params>
    struct IsFunctionT<R (Params..., ...) const&> : std::true_type {
        using Type = R;
        using ParamsT = Typelist<Params...>;
        static constexpr bool variadic = true;
    };
    ...
    

    8.4 判断类类型

    • 不同于已经处理过的其他组合类型,类类型没有局部特化模式,也不能像对基本类型那样枚举所有类类型。这里需要用一个间接的方法识别类类型——提出一些对所有类类型都有效的类型或表达式,对其可以应用SFINAE
    • 类类型最方便利用的属性是,只有类类型能被用作pointer-to-member类型的基础,即在Y::*这个构造中,Y只能为类类型
    // traits/isclass.hpp
    
    #include <type_traits>
    
    template<typename T, typename = std::void_t<>>
    struct IsClassT : std::false_type { // primary template: by default no class
    };
    
    template<typename T>
    struct IsClassT<T, std::void_t<int T::*>> // classes can have pointer-to-member
    : std::true_type {
    };
    
    • lambda表达式是一个unique unnamed non-union class type,因此检查lambda表达式是否为类类型对象时将产生true。注意,表达式int T::*对union类型也有效,union也是一种类类型
    auto l = []{};
    static_assert<IsClassT<decltype(l)>::value, "">; // succeeds
    
    • 标准库提供了std::is_class<>和std::is_union<>
    • 下面是C++98中确定类类型的方法
    #include <iostream>
    
    template<typename T>
    class IsClassT {
    private:
        typedef char One;
        typedef struct { char a[2]; } Two;
        template<typename C> static One test(int C::*);
        template<typename C> static Two test(...);
    public:
        enum { Yes = sizeof(IsClassT<T>::test<T>(0)) == 1 };
        enum { No = !Yes };
    };
    
    template<typename T>
    void check()
    {
        if (IsClassT<T>::Yes) std::cout << "Y" << std::endl;
        else std::cout << "N" << std::endl;
    }
    
    template<typename T>
    void checkT (T)
    {
        check<T>();
    }
    
    class MyClass {};
    struct MyStruct {};
    union MyUnion {};
    void myfunc() {}
    enum E{e1}e;
    
    int main()
    {
        check<int>(); // N
        check<MyClass>(); // Y
        MyStruct s;
        checkT(s); // Y
        check<MyUnion>(); // Y
        checkT(e); // N
        checkT(myfunc); // N
    }
    

    8.5 判断枚举类型

    • 测试枚举类型,直接用SFINAE-based traits检查一个到int的显式转换,并显式排除基本类型、类类型、引用类型、指针类型和pointer-to-member类型,所有这些能转为int的都不是枚举类型。简单来说就是,不属于其他任何类别的类型就是枚举类型
    // traits/isenum.hpp
    
    template<typename T>
    struct IsEnumT {
        static constexpr bool value = !IsFundaT<T>::value &&
            !IsPointerT<T>::value &&
            !IsReferenceT<T>::value &&
            !IsArrayT<T>::value &&
            !IsPointerToMemberT<T>::value &&
            !IsFunctionT<T>::value &&
            !IsClassT<T>::value;
    };
    
    • 标准库提供了std::is_enum<>

    09 Policy traits

    • 目前给出的traits模板例子都是用于去确定模板参数的一些属性,如这些参数表示的类型,混合类型的操作中需要提升的类型,这些traits称为property traits。另外还存在其他类型的traits,定义了如何对待这些类型,这类traits称为policy traits,policy traits针对的是模板参数相关的更加独有的属性。通常把property traits实现为类型函数,但policy traits通常把policy封装在成员函数内部

    9.1 只读的参数类型

    • 不能预测替换模板参数的类型大小,而一个小的结构也可能有高开销的拷贝构造函数,这时应该以const引用方式传递只读参数。使用policy traits模板可以处理这个问题,它实际上是一个类型函数,这个函数可以根据类型大小把实参类型T映射为T或T const&。下面的例子假设对不大于2个指针大小的类型,基本模板pass-by-value,对其他类型则传const引用
    template<typename T>
    class RParam {
    public:
        using Type = typename IfThenElseT<sizeof(T)<=2*sizeof(void*),
            T,
            T const&>::ResultT Type;
    };
    
    • 另一方面,对容器类型,即使sizeof很小也可能涉及昂贵的拷贝构造函数,因此需要如下的许多特化和局部特化
    template<typename T>
    struct RParam<Array<T>> {
        using Type = Array<T> const&;
    };
    
    • 因为处理的是常见类型,所以希望在基本模板中对nonclass type传值调用,对某些性能要求严格的class type则有选择地添加这些类为传值方式
    // traits/rparam.hpp
    
    #ifndef RPARAM_HPP
    #define RPARAM_HPP
    
    #include "ifthenelse.hpp"
    #include <type_traits>
    
    template<typename T>
    struct RParam {
        using Type
            = IfThenElse<(sizeof(T) <= 2*sizeof(void*)
                && std::is_trivially_copy_constructible<T>::value
                && std::is_trivially_move_constructible<T>::value),
                T,
                T const&>;
    };
    #endif // RPARAM_HPP
    
    • 对于上面两种方法的任意一种,都可以在traits模板的定义中实现这个policy,客户端能使用这个policy获得更好的性能。比如假设有两个类,其中一个指定对只读实参,传值性能更好
    // traits/rparamcls.hpp
    
    #include "rparam.hpp"
    #include <iostream>
    
    class MyClass1 {
    public:
        MyClass1 () {
        }
        MyClass1 (MyClass1 const&) {
            std::cout << "MyClass1 copy constructor called\n";
        }
    };
    
    class MyClass2 {
    public:
        MyClass2 () {
        }
        MyClass2 (MyClass2 const&) {
            std::cout << "MyClass2 copy constructor called\n";
        }
    };
    
    // pass MyClass2 objects with RParam<> by value
    template<>
    class RParam<MyClass2> {
    public:
        using Type = MyClass2;
    };
    
    • 现在对具有只读实参的函数就可以在函数声明中使用RParam<>,并调用这些函数
    // traits/rparam1.cpp
    
    #include "rparam.hpp"
    #include "rparamcls.hpp"
    
    // function that allows parameter passing by value or by reference
    template <typename T1, typename T2>
    void foo (typename RParam<T1>::Type p1,
        typename RParam<T2>::Type p2)
    {
        ...
    }
    
    int main()
    {
        MyClass1 mc1;
        MyClass2 mc2;
        foo<MyClass1,MyClass2>(mc1,mc2);
    }
    
    • 但使用RParam的做法有几个严重缺点,函数声明变得格外复杂, 其次不能使用实参推断来调用类似foo()这样的函数,因为模板参数只出现在函数参数的限定符中,因此必须在调用的位置显式指定模板实参。有一个笨拙的解决方法是,使用一个内联的wrapper函数模板,但该方案假设内联函数会被编译器移除,即编译器直接调用位于内联函数中的函数
    // traits/rparam2.cpp
    
    #include "rparam.hpp"
    #include "rparamcls.hpp"
    
    // function that allows parameter passing by value or by reference
    template <typename T1, typename T2>
    void foo_core (typename RParam<T1>::Type p1, typename RParam<T2>::Type p2)
    {
        ...
    }
    
    // wrapper to avoid explicit template parameter passing
    template<typename T1, typename T2>
    void foo (T1 && p1, T2 && p2)
    {
        foo_core<T1,T2>(std::forward<T1>(p1),std::forward<T2>(p2));
    }
    
    int main()
    {
        MyClass1 mc1;
        MyClass2 mc2;
        foo(mc1,mc2); // same as foo_core<MyClass1,MyClass2>(mc1,mc2)
    }
    

    9.2 拷贝、交换和移动(第一版内容)

    • 这里介绍一个policy traits模板,它将选择出最佳的操作,来拷贝、交换或移动某一特定类型的元素
    • 期望一个traits模板,对于泛型定义区分class type和nonclass type,因为对nonclass type不需要关心用户定义的copying操作,这里使用继承来在两种traits中进行选择
    // traits/csmtraits.hpp
    template <typename T>
    class CSMtraits : public BitOrClassCSM<T, IsClassT<T>::No > {
    };
    
    • CSMtraits(copy、swap、move的traits)的实现委托给了BitOrClassCSM<>的特化,基类的第二个模板参数表示是否能安全地使用bitwise copying实现多种操作。泛型定义假设不能对class类型安全地使用bitwise copying,但对POD类型可以特化CSMtraits获得更好的性能
    template<>
    class CSMtraits<MyPODType>
    : public BitOrClassCSM<MyPODType, true> {
    };
    
    • BitOrClassCSM模板在缺省情况下包含两个局部特化,下面是一个基本模板和一个不使用bitwise copying的安全的局部特化
    // traits/csm1.hpp
    
    #include <new>
    #include <cassert>
    #include <stddef.h>
    #include "rparam.hpp"
    
    // primary template
    template<typename T, bool Bitwise>
    class BitOrClassCSM;
    
    // partial specialization for safe copying of objects
    template<typename T>
    class BitOrClassCSM<T, false> {
    public:
        static void copy (typename RParam<T>::ResultT src, T* dst) {
            // copy one item onto another one
            *dst = src;
        }
    
        static void copy_n (T const* src, T* dst, size_t n) {
            // copy n items onto n other ones
            for (size_tk=0;k<n; ++k) {
                dst[k] = src[k];
            }
        }
    
        static void copy_init (typename RParam<T>::ResultT src, void* dst) {
            // copy an item onto uninitialized storage
            ::new(dst) T(src);
        }
    
        static void copy_init_n (T const* src, void* dst, size_t n) {
            // copy n items onto uninitialized storage
            for (size_tk=0;k<n; ++k) {
                ::new((void*)((char*)dst+k)) T(src[k]);
            }
        }
    
        static void swap (T* a, T* b) {
            // swap two items
            T tmp(a);
            *a = *b;
            *b = tmp;
        }
    
        static void swap_n (T* a, T* b, size_t n) {
            // swap n items
            for (size_tk=0;k<n; ++k) {
                T tmp(a[k]);
                a[k] = b[k];
                b[k] = tmp;
            }
        }
    
        static void move (T* src, T* dst) {
            // move one item onto another
            assert(src != dst);
            *dst = *src;
            src->~T();
        }
    
        static void move_n (T* src, T* dst, size_t n) {
            // move n items onto n other ones
            assert(src != dst);
            for (size_tk=0;k<n; ++k) {
                dst[k] = src[k];
                src[k].~T();
            }
        }
    
        static void move_init (T* src, void* dst) {
            // move an item onto uninitialized storage
            assert(src != dst);
            ::new(dst) T(*src);
            src->~T();
        }
    
        static void move_init_n (T const* src, void* dst, size_t n) {
            // move n items onto uninitialized storage
            assert(src != dst);
            for (size_tk=0;k<n; ++k) {
                ::new((void*)((char*)dst+k)) T(src[k]);
                src[k].~T();
            }
        }
    };
    
    • 上面policy traits模板成员函数都是静态的,因为只是对参数类型的对象应用这些成员函数,而并非对traits class类型对象使用
    • 另一个针对bitwise copying的traits实现的局部特化如下
    // traits/csm2.hpp
    
    #include <cstring>
    #include <cassert>
    #include <stddef.h>
    #include "csm1.hpp"
    
    // partial specialization for fast bitwise copying of objects
    template <typename T>
    class BitOrClassCSM<T,true> : public BitOrClassCSM<T,false> {
    public:
        static void copy_n (T const* src, T* dst, size_t n) {
            // copy n items onto n other ones
            std::memcpy((void*)dst, (void*)src, n);
        }
    
        static void copy_init_n (T const* src, void* dst, size_t n) {
            // copy n items onto uninitialized storage
            std::memcpy(dst, (void*)src, n);
        }
    
        static void move_n (T* src, T* dst, size_t n) {
            // move n items onto n other ones
            assert(src != dst);
            std::memcpy((void*)dst, (void*)src, n);
        }
    
        static void move_init_n (T const* src, void* dst, size_t n) {
            // move n items onto uninitialized storage
            assert(src != dst);
            std::memcpy(dst, (void*)src, n);
        }
    };
    

    10 In the Standard Library

    • C++11中,type traits变成了标准库的内置部分。他们或多或少包含上述所有类型函数和type traits,但对于其中一些,比如trivial operation detection traits和std::is_union,没有已知的in-language solution,而编译器对这些traits提供了内置支持。此外即使有in-language solution来缩短编译期,编译器也开始支持traits。因此如果需要type traits,只要可行就推荐使用标准库
    • 标准库也定义了一些policy和property traits
      • 类模板std::char_traits被string和I/O stream类用作一个policy traits参数
      • 为了轻易适配算法给标准迭代器,提供了一个非常简单的property traits模板std::iterator_traits
      • 模板std::numeric_limits也能被用作property traits模板
      • 最后,内存分配器通过使用policy traits类处理,C++98开始就提供了模板std::allocator作为此目的的标准组件,C++11添加了模板std::allocator_traits来允许改变分配器的policy/behavior

    相关文章

      网友评论

        本文标题:【C++ Templates(17)】Traits的实现(下)

        本文链接:https://www.haomeiwen.com/subject/gzodeftx.html