• C++中的模板元编程


    目录

    概述

    模板元编程可以说是C++中最困难也是最强大的编程范式。模版元编程不同于普通的运行期程序,它执行完全是在编译期,并且它操纵的数据不能是运行时变量,只能是编译期常量(且不可修改)。因此,模版元编程需要很多技巧,在这里你会遇到非常多的枚举常量、继承、模板偏特化、可变模板参数、类型萃取等方法,如果要读懂STL库,那么模板元编程必须要掌握;不仅如此,C++中的许多黑科技都是依赖于模板元编程的,例如我们甚至可以写出编译期排序的算法

    模板元编程:template meta programing

    type_traits-定义元数据

    <type_traits>是C++11提供的模板元基础库,它提供了模板元编程中需要的常用的基础元函数

    std::integral_constant,定义编译期常量

    举个例子,C++11中提供了std::integral_constant来定义编译期常量

    template<typename T>
    using one_constant = std::integral_constant<T, 1>;
    
    template<typename T>
    struct one_struct : std::integral_constant<T, 1> {};
    

    因此我们可以使用one_constant<int>::valueone_struct<int>::value来获取编译期常量int 1

    而在C++11之前,我们定义这个常量就需要用到enum或static-const

    struct one_struct
    {
        enum { value = 1 };
    };
    
    struct one_struct
    {
        static const int value = 1;  
    };
    

    然后通过one_struct::value来访问值,其中enum能隐式转换为int

    std::integral_constant的实现也非常的简单

    template <class _Ty, _Ty _Val>
    struct integral_constant {
        static constexpr _Ty value = _Val;
    
        using value_type = _Ty;
        using type       = integral_constant;
    
        constexpr operator value_type() const noexcept {
            return value;
        }
    
        _NODISCARD constexpr value_type operator()() const noexcept {
            return value;
        }
    };
    

    可以看到,通过C++11的<type_traits>提供的一个简单的std::integral_constant就可以很方便的定义编译期常量,而无需再去使用enum和static-const。紧接着,库中又提供了编译期常量的bool类型

    template <bool _Val>
    using bool_constant = integral_constant<bool, _Val>;
    
    using true_type  = bool_constant<true>;
    using false_type = bool_constant<false>;
    

    std::integer_sequence

    std::is_integral_v,判断是否为整形

    std::is_integral_v,根据名称可以看出它可以用于判断T是否为integral类型,从_v可以得出它是一个元数据。std::is_integral_v在对T去除cv限定符后,在一堆integral类型中进行匹配比较,最后萃取出一个bool编译期常量

    // STRUCT TEMPLATE is_integral
    template <class _Ty>
    inline constexpr bool is_integral_v = _Is_any_of_v<remove_cv_t<_Ty>, bool, char, signed char, unsigned char,
        wchar_t,
    #ifdef __cpp_char8_t
        char8_t,
    #endif // __cpp_char8_t
        char16_t, char32_t, short, unsigned short, int, unsigned int, long, unsigned long, long long, unsigned long long>;
    

    那么仅仅有一个“值”是不够的,我们还需要一个类型,那就是std::is_integral,这里从“值”到“类型”的“逆”转换很巧妙

    为什么说是“逆”转换呢,因为这个类的声明顺序是现有std::is_integral_v,后有std::is_integral

    template <class _Ty>
    struct is_integral : bool_constant<is_integral_v<_Ty>> {};
    
    // true 两者都派生自std::bool_constant<false>
    std::cout << std::boolalpha << std::is_base_of_v<std::false_type, std::is_integral<std::string>> << std::endl;
    // true 因为long long是整形类型
    std::cout << std::boolalpha << std::is_integral_v<long long> << std::endl;
    

    std::integer_sequence,定义编译期整数序列

    为什么说是整形呢,请看源码。如果不是整形类型那么IDE和编译期会给出静态断言报错

    // 一个类型加上一个非类型模板参数包
    template <class _Ty, _Ty... _Vals>
    struct integer_sequence { // sequence of integer parameters
        static_assert(is_integral_v<_Ty>, "integer_sequence<T, I...> requires T to be an integral type.");
    
        using value_type = _Ty;
    
        static constexpr size_t size() noexcept {
            return sizeof...(_Vals);
        }
    };
    

    来通过一个简单的例子来了解它

    template<typename T, T... Is>
    void print_sequence(std::integer_sequence<T, Is...> sequence)
    {
        ((std::cout << sequence.size() << "elements: ") << ... << Is);
    }
    
    int main()
    {
        // 6 elements: 4328910
        print_sequence(std::integer_sequence<unsigned int, 4, 3, 2, 8, 9, 10>());
        // 10 elements: 0123456789
        print_sequence(std::make_integer_sequence<int, 10>());
        // 10 elements: 0123456789
        print_sequence(std::make_index_sequence<10>());
    }
    

    其他的相关源码为

    // 构建一个std::integer_sequence
    // ALIAS TEMPLATE make_integer_sequence
    template <class _Ty, _Ty _Size>
    using make_integer_sequence = __make_integer_seq<integer_sequence, _Ty, _Size>;
    
    template <size_t... _Vals>
    using index_sequence = integer_sequence<size_t, _Vals...>;
    
    // 构建一个std::integer_sequence<std::size_t>
    template <size_t _Size>
    using make_index_sequence = make_integer_sequence<size_t, _Size>;
    
    template <class... _Types>
    using index_sequence_for = make_index_sequence<sizeof...(_Types)>;
    
    • std::make_integer_sequence能创建一个0-_Size - 1的序列,需要注意的是,它并不是一个可调用的函数,而是一个type alias(模板别名)

    • std::index_sequencestd::size_t类型的std::integer_sequence

    • std::index_sequence_for将以参数包的个数作为序列的_Size,创建一个0-_Size - 1的序列

      // 5 elements: 01234
      print_sequence(std::index_sequence_for<float, std::iostream, char, double, std::shared_ptr<std::string>>{});	// 参数包大小为5
      

    利用编译期序列实现数组到元组的转换

    这是一个cppreference上的例子

    template<typename Array, std::size_t... Is>
    auto array_to_tuple_impl(const Array& _array, std::index_sequence<Is...> _sequence)
    {
        return std::make_tuple(_array[Is]...);
    }
    
    template<typename T, std::size_t I, typename Sequence = std::make_index_sequence<I>>
    auto array_to_tuple(const std::array<T, I> _array)
    {
        return array_to_tuple_impl(_array, Sequence{});
    }
    
    int main()
    {
        std::array<int, 3> a{12, 3, 2};
    
        auto full_t = array_to_tuple(a);
        auto part_t = array_to_tuple<int, 3, std::index_sequence<1, 2>>(a);
        
        // 32
        std::apply([](auto&&... obj) { ((std::cout << obj << " "), ...); }, full_t);
    }
    

    但是这个输出std::tuple的方法并不完美,它会在输出的末尾添加一个不必要的" ",所以在此之上进行一层编译期序列的封装,能够判断此时解析到元组的第几个元素

    template<typename Tuple, std::size_t... Is>
    std::ostream& print_tuple_impl(std::ostream& os, const Tuple& tuple, std::index_sequence<Is...> sequence)
    {
        return ((os << (Is == 0 ? "" : " ") << std::get<Is>(tuple)), ...);
    }
    
    template<typename... Ts>
    std::ostream& operator<<(std::ostream& os, std::tuple<Ts...> tuple)
    {
        return print_tuple_impl(os, tuple, std::index_sequence_for<Ts...>{});
        // return print_tuple_impl(os, tuple, std::make_index_sequence<sizeof...(Ts)>{});
    }
    
    // perfect print
    std::cout << full_t << std::endl;	// 12 3 2
    

    type_traits-判断元数据

    std::is_array,判断是否数组类型

    源码十分简单,根据特化类型来判断数组类型

    // TYPE PREDICATES
    template <class>
    inline constexpr bool is_array_v = false; // determine whether type argument is an array
    
    template <class _Ty, size_t _Nx>
    inline constexpr bool is_array_v<_Ty[_Nx]> = true;
    
    template <class _Ty>
    inline constexpr bool is_array_v<_Ty[]> = true;
    
    // STRUCT TEMPLATE is_array
    template <class _Ty>
    struct is_array : bool_constant<is_array_v<_Ty>> {};
    

    测试代码

    float arr[10]{};
    // true
    std::cout << std::boolalpha << std::is_array_v<decltype(arr)> << std::endl;
    // C++20 arr是一个有边界的数组 故输出false
    std::cout << std::boolalpha << std::is_unbounded_array_v<decltype(arr)> << std::endl;
    // false
    std::cout << std::boolalpha << std::is_array_v<std::array<int, 10>> << std::endl;
    

    std::is_same,判断两种类型是否相同

    template <class, class>
    inline constexpr bool is_same_v = false; // determine whether arguments are the same type
    template <class _Ty>
    inline constexpr bool is_same_v<_Ty, _Ty> = true;
    
    template <class _Ty1, class _Ty2>
    struct is_same : bool_constant<is_same_v<_Ty1, _Ty2>> {};
    

    测试代码

    // 一般情况int都占4B true
    std::cout << std::boolalpha << std::is_same_v<int, std::int32_t> << std::endl;
    // true
    std::cout << std::boolalpha << std::is_same_v<int, signed int> << std::endl;
    

    更多类型判断

    更多内容可以查看文档:cppreference-类型属性

    type_traits-类型修改

    根据上文中的std::is_array,我们可以看到原理是对不同的类型参数做出不同的特化。现在我们还可以利用特化做点别的事情

    std::remove_const,移除类型的const属性

    // STRUCT TEMPLATE remove_const
    template <class _Ty>
    struct remove_const { // remove top-level const qualifier
        using type = _Ty;
    };
    
    template <class _Ty>
    struct remove_const<const _Ty> {
        using type = _Ty;
    };
    
    template <class _Ty>
    using remove_const_t = typename remove_const<_Ty>::type;
    

    测试代码为

    std::remove_const_t<const int> data = 100;
    data = 10;
    

    std::decay,朽化

    std::is_const,判断此类型是否是const限定的

    std::is_const的实现是

    // STRUCT TEMPLATE is_const
    template <class>
    inline constexpr bool is_const_v = false; // determine whether type argument is const qualified
    
    template <class _Ty>
    inline constexpr bool is_const_v<const _Ty> = true;
    
    // true
    std::cout << std::boolalpha << std::is_const_v<const int> << std::endl;
    // false
    std::cout << std::boolalpha << std::is_const_v<const int&> << std::endl;
    // false
    std::cout << std::boolalpha << std::is_const_v<const int(std::string, double)> << std::endl;
    // false 因为是底层指针 指针本身是可以被修改的
    std::cout << std::boolalpha << std::is_const_v<const int*> << std::endl;
    // true 因为是顶层指针 指向的指向不能更改
    std::cout << std::boolalpha << std::is_const_v<int* const> << std::endl;
    

    这一句话很重要

    determine whether type argument is const qualified

    const int&为例,在调用到is_const_v<T>时,T被推导为intconst intconst int&不符,因此调用不到true版本的特化,所以结果为false

    因此当一个类型的const修饰无意义并且它不是引用类型的时候,它是函数类型,如<int(std::string, double)>

    std::is_function,判断是否为函数类型

    // STRUCT TEMPLATE is_function
    template <class _Ty>
    inline constexpr bool is_function_v = // only function types and reference types can't be const qualified
        !is_const_v<const _Ty> && !is_reference_v<_Ty>;
    
    template <class _Ty>
    struct is_function : bool_constant<is_function_v<_Ty>> {};
    

    这一句话很重要

    only function types and reference types can't be const qualified

    std::remove_extent,移除一层数组

    如果是一维数组,那么获取它的元素类型;如果是高维数组,那么移除第一层数组

    // true
    std::cout << std::boolalpha << std::is_same_v<std::remove_extent_t<const int[]>, const int> << std::endl;
    // true
    std::cout << std::boolalpha << std::is_same_v<std::remove_extent_t<int[][10]>, int[10]><< std::endl;
    

    朽化的具体步骤

    对于最终的结果typestd::decay做了很多工作

    // STRUCT TEMPLATE decay
    template <class _Ty>
    struct decay { // determines decayed version of _Ty
        using _Ty1 = remove_reference_t<_Ty>;
        using _Ty2 = typename _Select<is_function_v<_Ty1>>::template _Apply<add_pointer<_Ty1>, remove_cv<_Ty1>>;
        using type = typename _Select<is_array_v<_Ty1>>::template _Apply<add_pointer<remove_extent_t<_Ty1>>, _Ty2>::type;
    };
    
    template <class _Ty>
    using decay_t = typename decay<_Ty>::type;
    
    • 首先移除类型的引用类型,记为_Ty1
    • 如果_Ty1是函数类型,那么在_Ty1的基础上加一层指针,记为_Ty2,否则_Ty2为移除cv限定符的_Ty1
    • 如果_Ty1是数组类型,那么记type为移除一层数组且加上一层指针的_Ty1,否则记type_Ty2

    例子

    std::decay_t<int> A;           // int
    std::decay_t<int&> B;          // int
    std::decay_t<int&&> C;         // int
    std::decay_t<const int&> D;    // int
    std::decay_t<int[10]> E;        // int*
    std::decay_t<int(int, int)> F;	// int(*)(int, int)
    

    std::_Add_reference,添加引用类型

    这个元函数也有必要挑出来讲一下,实现也是非常的巧妙

    // STRUCT TEMPLATE _Add_reference
    template <class _Ty, class = void>
    struct _Add_reference { // add reference (non-referenceable type)
        using _Lvalue = _Ty;
        using _Rvalue = _Ty;
    };
    
    template <class _Ty>
    struct _Add_reference<_Ty, void_t<_Ty&>> { // (referenceable type)
        using _Lvalue = _Ty&;
        using _Rvalue = _Ty&&;
    };
    

    重点是这个void_t<_Ty&>。首先。void类型是无法添加左值引用或右值引用的,如void&void&&都会导致编译错误。因此void_t<_Ty&>其实实现了一个substitution failure,对于_Add_reference<void>来说,并不会导致报错,再“添加”了引用后void仍然是void

    The major difference to directly using T& is that std::add_lvalue_reference<void>::type is void, while void& leads to a compilation error.

    SOF-is-only-void-a-non-referenceable-type

    更多类型修改

    更多内容可以查看文档:cppreference-类型属性

    编译期判断

    上文中提到过,模板元编程中没有if-else这些判断语句,但是<type_traits>中提供了不少元函数来实现这些功能

    std::conditional,三目运算符

    源码实现也非常的简单,都是技巧

    // STRUCT TEMPLATE conditional
    template <bool _Test, class _Ty1, class _Ty2>
    struct conditional { // Choose _Ty1 if _Test is true, and _Ty2 otherwise
        using type = _Ty1;
    };
    
    template <class _Ty1, class _Ty2>
    struct conditional<false, _Ty1, _Ty2> {
        using type = _Ty2;
    };
    
    template <bool _Test, class _Ty1, class _Ty2>
    using conditional_t = typename conditional<_Test, _Ty1, _Ty2>::type;
    
    // 是不是感觉很眼熟
    int a = (100 > 50) ? 100 : 50; 
    
    using IntType = std::conditional_t<true, int, float>;
    using FloatType = std::conditional_t<false, int, float>;
    

    注意,该原函数计算的参数和结果都是类型

    std::enable_if,if-true表达式

    源码也是非常的简单

    // STRUCT TEMPLATE enable_if
    template <bool _Test, class _Ty = void>
    struct enable_if {}; // no member "type" when !_Test
    
    template <class _Ty>
    struct enable_if<true, _Ty> { // type is _Ty for _Test
        using type = _Ty;
    };
    
    template <bool _Test, class _Ty = void>
    using enable_if_t = typename enable_if<_Test, _Ty>::type;
    

    它只实现了true时期的特化,而当条件为false时,std::enable_if为空类,试图调用不存在的type将会导致编译出错

    限制模板参数的参数类型

    在C#中存在一个where关键字可以用来限制泛型的类型,C++中同样可以实现这一点

    在小学数学中,我们称能被2整除的整数为偶数,聪明的读者可能会认为应该这么写

    // 只有整形才有%运算符
    template<typename T>
    bool is_odd(std::enable_if_t<std::is_integral_v<T>, T> num)
    {
        return num % 2 == 0;
    }
    

    但是这样会导致一个问题,函数模板没有办法自动推导参数的类型

    std::cout << std::boolalpha << is_odd<int>(20) << std::endl;
    

    所以我们应该把类型判断放到返回值中

    template<typename T>
    std::enable_if_t<std::is_integral_v<T>, bool> is_odd(T num)
    {
        return num % 2 == 0;
    }
    
    std::cout << std::boolalpha << is_odd(20) << std::endl;
    // std::cout << std::boolalpha << is_odd(20.0) << std::endl;	// 编译出错
    

    或者把它当作模板函数的匿名默认参数

    template<typename T>
    bool is_odd(T data, std::enable_if_t<std::is_integral_v<T>, void>* = nullptr)
    {
        return data % 2 == 0;
    }
    

    匹配函数重载或偏特化类

    SFINAE

    SFINAE:Substitution failure is not an error,替代失败不代表是个错误

    知乎-SFINAE

    • error,在这里指编译错误,当出现编译错误的时候会停止代码的链接等后续活动。如上述例子中判断偶数的模板函数中,若函数参数非整形类型,那么会出现error

      std::cout << std::boolalpha << is_odd(20.0) << std::endl;
      
    • failure,当失败发生时不代表编译出错,当我们使用switchif-else或是函数重载时,failure随处可见,这里以函数重载为例

      void foo(std::size_t) {}
      void foo(std::string) {}
      
      foo("Jelly");
      foo((int*)nullptr);
      

      当我们执行第一个foo时,我们可以认为它对std::size_t的调用是一个failure,然后成功的调用到了std::string的版本

      当我们执行第二个foo时,我们可以认为它出现了两次failure,并且两个版本的函数都无法调用,这时出现了error,编译器给出报错

    所以说,当我们遇到failure时,编译器往往还会尝试其他解决问题的方法,只有无路可走时才会出现error,这就是failure is not an error

    所谓Substitution,就是将函数模板中的形参替换为实参的过程,来看一个例子,照搬自知乎

    struct X {
    	typedef int type1;
    };
    
    struct Y {
    	typedef int type2;
    };
    
    // foo1
    template<typename T> 
    void foo(typename T::type1) {}
    
    // foo2
    template<typename T> 
    void foo(typename T::type2) {}
    
    // foo3
    template<typename T> 
    void foo(T) {}
    
    int main()
    {
    	foo<X>(5);    // foo1: Succeed, foo1: Failed,  foo2: Failed
    	foo<Y>(10);   // foo1: Failed,  foo1: Succeed, foo2: Failed
    	foo<int>(15); // foo1: Failed,  foo1: Failed,  foo2: Succeed
    }
    

    很显然,在调用foo<Y>(10)时,对于foo1而言实参明显无法匹配到形参上,但我们此时并不能武断的说发生了error,而应该认为发生了substitution failure,然后编译期会尝试下一个函数重载

    利用SFINAE进行匹配

    假设我们实现了一个接口和一个类,其中有increase的接口

    struct ICounter  {
    	virtual void increase() = 0;
    	virtual ~ICounter() {}
    };
    
    struct Counter : public ICounter  {
    	virtual void increase() override {
    		// code...
    	}
    };
    

    那么现在我想有一个函数,我希望它能够同时处理整形类型和Counter的累加操作

    template<typename T>
    void increase_fuc(T& counter, std::enable_if_t<std::is_base_of_v<ICounter, T>, void>* = nullptr) {
        counter.increase();
    }
    
    template<typename T>
    void increase_fuc(T& num, std::enable_if_t<std::is_integral_v<T>, void>* = nullptr) {
        ++num;
    }
    
    int data = 0;
    Counter c;
    increase_fuc(data);
    increase_fuc(c);
    

    利用Expression SFINAE进行匹配

    你可能觉得上文中使用虚函数会有点消耗性能,又或者你不想使用接口

    struct Counter {
    	void increase() {
    		// code...
    	}
    };
    

    C++11支持Expression SFINAE,让我们可以不依赖于std::enable_if写出函数重载,而使用decltype

    template<typename T>
    void increase_fuc(T& counter, std::decay_t<decltype(counter.increase())>* = nullptr) {
        // 注意这里的decltype仅起到推断作用 并不会真正执行
        counter.increase();
    }
    
    template<typename T>
    void increase_fuc(T& num, std::decay_t<decltype(++num)>* = nullptr) {
        // 只有支持++操作的类型才能调用到这个重载
        ++num;
    }
    

    由于int&*是非法的,因此需要加朽化修饰

    // 那么对于int类型而言 在调用第一个重载时就发生了Expression Substitution Failure
    int data = 0;
    increase_fuc(data);
    

    或者,你还可以这么写来避免默认参数的使用,虽然说std::is_integral_v<T>的效果和 decltype(++)不同,非整形类型的也可能支持++操作,这里仅仅是给出个参考

    template<typename T>
    std::enable_if_t<std::is_integral_v<T>, void> increase_fuc(T& num) {
        // 只有支持++操作的类型才能调用到这个重载
        ++num;
    }
    
    template<typename T>
    auto increase_fuc(T& num) -> decltype(void(++num)) {
        // 只有支持++操作的类型才能调用到这个重载
        ++num;
    }
    

    使用C++17中的if-constexpr

    template<typename T>
    void increase_fuc(T& counter) {
        if constexpr(std::is_base_of_v<ICounter, T>)
            counter.increase();
        else if (std::is_integral_v<T>)
            ++counter;
    }
    

    std::void_t

    std::void_t并不单单是void那么简单

    This metafunction is used in template metaprogramming to detect ill-formed types in SFINAE context:

    detect ill-formed types:检测缪构类型,缪构,可以理解为不合法

    判断某类型中是否using了某type

    template<typename, typename = void>
    struct has_using_type : std::false_type {};
    
    template<typename T>
    struct has_using_type<T, std::void_t<typename T::type>> : std::true_type {};
    
    class PrivateUsing {
        using type = int;
    };
    
    int main() {
        // false
        std::cout << std::boolalpha << has_using_type<PrivateUsing>::value << std::endl;
    }
    

    判断某类型是否含有public的某成员函数或某成员

    std::declval

    // FUNCTION TEMPLATE declval
    template <class _Ty>
    add_rvalue_reference_t<_Ty> declval() noexcept;
    

    很神奇,这个元函数并没有实现,它只有声明,说明这是一个只能用在编译期的元函数

    struct DefaultCtor
    {
        DefaultCtor()  { std::cout << "create" << std::endl; }
    };
    
    int main() {
        // 构建一个对象 输出create
        auto d = std::remove_reference_t<DefaultCtor>();
        // 因为元函数并没有被实现 因此编译错误
        // auto non_d = std::declval<DefaultCtor>();
    }
    

    所以std::declval一般都用于在decltype中,不用经过构造函数就能使用成员函数或成员变量(尽管std::remove_reference_t<DefaultCtor>()decltype中也不会实例化一个对象出来,且引用类型对访问成员并无影响,但仍然推荐使用std::declval,它会使代码更易读,让阅读者明白这是一个访问成员的用法)

    实例

    利用检测某类型是否具有operator->get成员函数来判断它是否是一个智能指针,以std::declval为例

    template<typename, typename = void>
    struct is_smart_pointer : std::false_type {};
    
    template<typename T>
    struct is_smart_pointer<T, std::void_t<decltype(std::declval<T>().operator->()), decltype(std::declval<T>().get())>> : std::true_type {};
    

    检测某类型是否具有data这个成员变量

    template<typename, typename = void>
    struct has_member_call_data : std::false_type {};
    
    template<typename T>
    struct has_member_call_data<T, std::void_t<decltype(std::declval<T>().data)>> : std::true_type {};
    
    class PrivateData {
        int data;
    };
    
    int main() {
        // false
        std::cout << std::boolalpha << has_member_call_data<PrivateData>::value << std::endl;
    }
    

    总结

    std::void_t的使用场景是“有没有”而不是“是不是

    手撕环节

    实现函数类型萃取类

    struct PlaceHolderTag {};
    struct FunctorTag {};
    struct BuildInTypeTag {};
    
    template<typename T, typename Holder = PlaceHolderTag>
    struct function_traits;
    
    template<typename T, typename... Ts>
    struct function_traits<T(Ts...)> {
        using ReturnType = T;
        using FunctionType = T(Ts...);
        using FunctionPointer = T(*)(Ts...);
    
        template<std::size_t I>
        struct args
        {
            static_assert(I < sizeof...(Ts), "args index out of range");
            using ParamType = std::tuple_element_t<I, std::tuple<Ts...>>;
        };
    };
    
    // std::function类型
    template<typename T, typename... Ts>
    struct function_traits<std::function<T(Ts...)>> : public function_traits<T(Ts...)> {};
    
    // 函数指针
    template<typename T, typename... Ts>
    struct function_traits<T(*)(Ts...)> : public function_traits<T(Ts...)> {};
    
    // 成员函数
    template<typename ClassType, typename T, typename...Ts>
    struct function_traits<T (ClassType::*)(Ts...)> : public function_traits<T(Ts...)> {};
    
    // 通过标签分发内置类型与仿函数
    template<typename T, typename Holder>
    struct function_traits : public function_traits<T, std::conditional_t<std::is_class_v<T>, FunctorTag, BuildInTypeTag>> {};
    
    // 内置类型
    template<typename BuildInType>
    struct function_traits<BuildInType, BuildInTypeTag>
    {
        static_assert(std::_Always_false<BuildInType>, "this is just a build in type");
    };
    
    // 仿函数
    template<typename ClassType>
    struct function_traits<ClassType, FunctorTag> : public function_traits<decltype(&ClassType::operator())> {};
    
    #define PRINT_TYPEID(T) std::cout << typeid(T).name() << std::endl
    
    int main()
    {
        std::function<int(std::string)> f;
        PRINT_TYPEID(function_traits<decltype(f)>::ReturnType);
        PRINT_TYPEID(function_traits<decltype(f)>::args<1>::ParamType);
        PRINT_TYPEID(function_traits<Test_Functor>::FunctionType);
    }
    

    但是对成员函数的重载并没有这么简单,对于常成员函数执行萃取操作,会导致编译错误

    struct Test_Functor
    {
        void non_const_func(double) {}
        void const_func(int) const {}
    };
    
    int main()
    {
        PRINT_TYPEID(function_traits<decltype(&Test_Functor::non_const_func)>::FunctionType);
        // 因为缺少对常函数的特化 所以其实会调用到内置类型的版本
        // PRINT_TYPEID(function_traits<decltype(&Test_Functor::const_func)>::ReturnType);
    }
    

    所以我们可以使用宏来减去模板的重复书写,其中宏中的__VA_ARGS__代表参数包...

    // 成员函数
    #define MEMBER_FUNCTION_TRAITS(...) 
    template<typename ClassType, typename T, typename...Ts> 
    struct function_traits<T (ClassType::*)(Ts...) __VA_ARGS__> : public function_traits<T(Ts...)> {}; 
    
    MEMBER_FUNCTION_TRAITS()
    MEMBER_FUNCTION_TRAITS(const)
    MEMBER_FUNCTION_TRAITS(volatile)
    MEMBER_FUNCTION_TRAITS(const volatile)
    

    实现std::variant

    在C++17中,文档中说道

    The class template std::variant represents a type-safe union

    但是std::variant的大小并不是数据包中的最大值,如果创建同类型的union,那么大小为8。因为std::variant自带内存对齐

    std::cout << sizeof(std::variant<int, bool, double>) << std::endl;	// 16
    

    当我们想要实现自己的variant时,首先需要实现三个点

    • 在编译期找出数据包中的最大值,以让运行期能够分配适量的空间
    • 赋值时需要根据原有的类型来调用析构函数,并调用placement new创建新的对象
    • 细节项(未实现):元素不能重复,自带store以及get的类型检查,检查类型是否存在于参数包中

    编译期计算,两个类型的最大者

    template<auto Left, auto Right>
    inline constexpr bool is_left_greater_than_right = Left > Right;
    
    template<typename Left, typename Right>
    struct binary_type_compare_max : std::conditional<is_left_greater_than_right<sizeof(Left), sizeof(Right)>, Left, Right> {};
    
    template<typename Left, typename Right>
    using binary_type_compare_max_t = typename binary_type_compare_max<Left, Right>::type;
    
    template<typename  T>
    inline void print_typeid() { std::cout << typeid(T).name() << std::endl; }
    
    int main()
    {
        print_typeid<binary_type_compare_max_t<int, double>>();
    }
    

    拓展,比较两个数据的大小并返回较大的那个

    template<auto Left, auto Right>
    inline constexpr bool is_left_greater_than_right = Left > Right;
    
    template<auto Left, auto Right>
    struct binary_size_compare_max : std::conditional<is_left_greater_than_right<Left, Right>,
            std::integral_constant<std::size_t, Left>, std::integral_constant<std::size_t, Right>> {};
    
    template<auto Left, auto Right>
    inline constexpr auto binary_size_compare_max_v = binary_size_compare_max<Left, Right>::type::value;
    
    int main()
    {
        std::cout << binary_size_compare_max_v<10, 30> << std::endl;
    }
    

    编译期计算,求一个类型参数包中size的最大者

    template<typename... Ts>
    struct multi_type_compare_max;
    
    template<typename T, typename U, typename... Ts>
    struct multi_type_compare_max<T, U, Ts...> : multi_type_compare_max<binary_type_compare_max_t<T, U>, Ts...> {};
    
    template<typename T>
    struct multi_type_compare_max<T> : std::integral_constant<std::size_t, sizeof(T)> {};
    
    template<typename... Ts>
    inline constexpr std::size_t multi_type_compare_max_v = multi_type_compare_max<Ts...>::value;
    
    int main()
    {
        // 最大size为8
        std::cout << multi_type_compare_max_v<int, double, std::int64_t, char> << std::endl;
    }
    

    编译期计算,根据index求参数包中的类型

    // 根据index获取type
    template<std::size_t index, typename...>
    struct my_variant_element;
    
    template<std::size_t index, typename T, typename... Ts>
    struct my_variant_element<index, T, Ts...> : my_variant_element<index - 1, Ts...> {
        static_assert(index < sizeof...(Ts) + 1, "index out of range");
    };
    
    template<typename T, typename... Ts>
    struct my_variant_element<0, T, Ts...> {
        using type = T;
    };
    
    template<std::size_t index, typename... Ts>
    using my_variant_element_t = typename my_variant_element<index, Ts...>::type;
    
    template<typename  T>
    inline void print_typeid() { std::cout << typeid(T).name() << std::endl; }
    
    int main()
    {
        // char
        print_typeid<my_variant_element_t<2, int, double, char, bool>>();
    }
    

    编译期计算,根据参数包中的类型求index

    实现一:求差

    // 根据type获取index
    template<typename, typename...>
    struct my_variant_index;
    
    template<typename Target, typename T, typename... Ts>
    struct my_variant_index<Target, T, Ts...> : std::conditional_t<std::is_same_v<Target, T>,
            std::integral_constant<std::size_t, sizeof...(Ts)>, my_variant_index<Target, Ts...>> {};
    
    // 匹配失败
    template<typename T>
    struct my_variant_index<T> {
        static_assert(std::_Always_false<T>, "cant find T in args");
    };
    
    template<typename Target, typename... Ts>
    inline constexpr std::size_t my_variant_index_v = sizeof...(Ts) - my_variant_index<Target, Ts...>::value - 1;
    
    int main()
    {
        // int位于参数包的第2位
        std::cout << my_variant_index_v<int, char, bool, int, long, long long, float, double> << std::endl;
    }
    

    实现二:正向递增

    // 根据type获取index
    template<std::size_t, typename, typename...>
    struct my_variant_index_impl;
    
    template<std::size_t index, typename Target, typename T, typename... Ts>
    struct my_variant_index_impl<index, Target, T, Ts...> : std::conditional_t<std::is_same_v<Target, T>,
            std::integral_constant<std::size_t, index>, my_variant_index_impl<index + 1, Target, Ts...>> {};
    
    // 匹配失败
    template<std::size_t index, typename T>
    struct my_variant_index_impl<index, T> {
        static_assert(std::_Always_false<T>, "cant find T in args");
    };
    
    template<typename Target, typename... Ts>
    inline constexpr std::size_t my_variant_index_v = my_variant_index_impl<0, Target, Ts...>::value;
    
    template<typename Target, typename... Ts>
    struct my_variant_index : std::integral_constant<std::size_t, my_variant_index_v<Target, Ts...>> {};
    

    实现三:某大佬解法

    运行期计算,析构内存上的数据

    template<typename... Ts>
    struct call_dtor_helper;
    
    template<typename T, typename... Ts>
    struct call_dtor_helper<T, Ts...> {
        void operator()(std::size_t index, void* memory)
        {
            if (index != 0)
                call_dtor_helper<Ts...>{}(index - 1, memory);
            else if (std::is_class_v<T>)
                reinterpret_cast<T*>(memory)->~T();
        }
    };
    
    template<>
    struct call_dtor_helper<> {
        void operator()(std::size_t, void*) {}
    };
    

    my_variant

    template<typename... Ts>
    class my_variant
    {
    public:
        void* memory;
        std::size_t index;
    public:
        my_variant() : memory(nullptr), index(0) {}
    
        template<typename T>
        my_variant(T&& data) : my_variant() {
            store(std::forward<T>(data));
        }
    
        ~my_variant() {
            if (memory == nullptr)
                return;
            call_dtor_helper<Ts...>{}(index, memory);
            ::operator delete(memory);
        }
    
        template<typename T>
        void store(T&& data) {
            // 无内存对齐
            if (memory == nullptr)
                memory = ::operator new(multi_type_compare_max_v<Ts...>);
            else
                call_dtor_helper<Ts...>{}(index, memory);
            ::new(memory) T(std::forward<T>(data));
            index = my_variant_index_v<T, Ts...>;
        }
    
        template<typename T>
        T& get() {
            return *reinterpret_cast<T*>(memory);
        }
    };
    

    测试代码

    尽管上文中写的很多编译期计算的代码都用不上,但是以学习为目的我并没有删除它们。本次手撕环节实现的my_variant功能很弱鸡,没有考虑拷贝以及移动的问题,仅供学习参考

    int main()
    {
        my_variant<long long, NonCopyStruct_Str> v(4ll);
        std::cout << v.get<long long>() << std::endl;
        v.store<NonCopyStruct_Str>((std::string)"Jelly");
        std::cout << v.get<NonCopyStruct_Str>().name << std::endl;
    }
    

    实现简单的type_list

    一个简单的type-list应该具有以下功能

    • push-front,pop-front
    • pop
    • size,empty,index-of
    template<typename... Ts>
    struct type_list;
    
    template<typename T, typename... Ts>
    struct type_list<T, Ts...> {};
    
    template<>
    struct type_list<> {};
    
    template<typename T>
    struct type_list_size;
    
    template<typename... Ts>
    struct type_list_size<type_list<Ts...>> : std::integral_constant<std::size_t, sizeof...(Ts)> {};
    
    template<typename T>
    inline constexpr std::size_t type_list_size_v = type_list_size<T>::value;
    
    template<typename T>
    struct type_list_pop_front;
    
    template<typename T, typename... Ts>
    struct type_list_pop_front<type_list<T, Ts...>> {
        using type = type_list<Ts...>;
    };
    
    template<typename T>
    using type_list_pop_front_t = typename type_list_pop_front<T>::type;
    
    template<typename NewT, typename T>
    struct type_list_push_front;
    
    template<typename NewT, typename... Ts>
    struct type_list_push_front<NewT, type_list<Ts...>> {
        using type = type_list<NewT, Ts...>;
    };
    
    template<typename NewT, typename... Ts>
    using type_list_push_front_t = typename type_list_push_front<NewT, Ts...>::type;
    
    template<typename T>
    struct type_list_clear;
    
    template<typename... Ts>
    struct type_list_clear<type_list<Ts...>> {
        using type = type_list<>;
    };
    
    template<typename T>
    using type_list_clear_t = typename type_list_clear<T>::type;
    
    template<typename T>
    struct is_type_list_empty : std::conditional_t<type_list_size_v<T> == 0, std::true_type, std::false_type> {};
    
    template<typename T>
    inline constexpr bool is_type_list_empty_v = is_type_list_empty<T>::value;
    
    template<std::size_t Index, typename T>
    struct type_list_index_of;
    
    template<std::size_t Index, typename T, typename... Ts>
    struct type_list_index_of<Index, type_list<T, Ts...>> : type_list_index_of<Index - 1, type_list<Ts...>> {};
    
    template<typename T, typename... Ts>
    struct type_list_index_of<0, type_list<T, Ts...>> {
        using type = T;
    };
    
    template<typename T, std::size_t Index>
    using type_list_index_of_t = typename type_list_index_of<Index, T>::type;
    
    template<typename T>
    struct type_list_front;
    
    template<typename T, typename... Ts>
    struct type_list<type_list<T, Ts...>> {
        using type = T;
    };
    
    template<typename T>
    using type_list_front_t = typename type_list_front<T>::type;
    
    template<typename T>
    struct type_list_back;
    
    template<typename... Ts>
    struct type_list_back<type_list<Ts...>> {
        using type = type_list_index_of_t<type_list<Ts...>, type_list_size_v<type_list<Ts...>> - 1>;
    };
    
    template<typename T>
    using type_list_back_t = typename type_list_back<T>::type;
    

    简单的测试代码为

    int main()
    {
        using emptyList = type_list<>;
        using myList = type_list<int, long long>;
        std::cout << typeid(type_list_back_t<myList>).name() << std::endl;
        std::cout << std::boolalpha << is_type_list_empty_v<emptyList> << std::endl;
        std::cout << std::boolalpha << std::is_same_v<type_list_pop_front_t<myList>, type_list_push_front_t<long long, emptyList>> << std::endl;
    }
    

    更多

    std::invoke_result,获取返回值类型(C++17起)

    struct TestResult {
        double operator()(char, int&) { return 2.0; }
        float operator()(int) { return 1.0; }
    };
    
    int main()
    {
        std::cout << typeid(std::invoke_result_t<TestResult, char, int&>).name() << std::endl;	// double
        std::cout << typeid(std::invoke_result_t<TestResult, int>).name() << std::endl;	// float
        std::cout << typeid(decltype(TestResult()(20))).name() << std::endl;	// float
    }
    

    模板元编程又两个部分组成:元数据和元函数。元数据是指编译期能在编译期处理的数据,即编译期常量;元函数指操纵元数据的函数,在编译期调用,它通常表现为一个模板类或一个模板函数

    模板元编程十分特殊,它无法使用if-elseforwhile这些运行期语句。但是我们可以通过std::enable_ifstd::integer_sequence等元函数来实现各种操作

    Template Type Deduction

    例一

    template<typename T>
    void foo(const T& data) {}
    
    template<typename T>
    void foo(T& data) {}
    
    int main()
    {
        int data = 100;
        const int& ref_data = data;
    
        foo(data);  // 调用到T& 其中T为int
        foo(ref_data);  // 调用到const T& 其中T为int
    }
    
    

    例二

    template<typename T>
    void foo(T& data) {}
    
    int main()
    {
        int data = 200;
        const int& ref_data = data;
    
        foo(ref_data);  // 调用到T& 其中T为const int
    }
    

    例三

    template<typename T>
    void foo(T data) {}
    
    int main()
    {
        int data = 200;
        const int& ref_data = data;
        const char* name = "Jelly";
    
        foo(ref_data);	// 其中T为int 执行了一次拷贝
        foo(name);	// 其中T为char* 执行了一个对指针的拷贝
    }
    

    模板推导类型的舍弃

    template<typename T>
    struct print_t
    {
        void print() { std::cout << std::boolalpha << "From T "   << std::is_same_v<T, const int&><< std::endl; }
    };
    
    template<typename T>
    struct print_t<const T>
    {
        void print() { std::cout << std::boolalpha << "From const T " << std::is_same_v<T, int&><< std::endl; }
    };
    
    template<typename T>
    struct print_t<T&>
    {
        void print() { std::cout << std::boolalpha << "From T& "  << std::is_same_v<T, const int><< std::endl; }
    };
    
    int main()
    {
        // From T& true
        print_t<const int&>().print();
    }
    

    如果我们把<T&>的特化移除,那么

    template<typename T>
    struct print_t
    {
        void print() { std::cout << std::boolalpha << "From T "   << std::is_same_v<T, const int&><< std::endl; }
    };
    
    template<typename T>
    struct print_t<const T>
    {
        void print() { std::cout << std::boolalpha << "From const T " << std::is_same_v<T, int&><< std::endl; }
    };
    
    int main()
    {
        // From T true
        print_t<const int&>().print();
       	// From const T false
        print_t<const int>().print();
    }
    

    print_t<const T>中的print,是不可能输出true的,不信的话读者可以自行尝试一下

    模板的底层实现是什么

    知乎-C++ 模板在编译过程上的实现原理是怎样的?

    学习资料

    知乎-C++11-17模板核心概念

    知乎-模板元编程

    github-qicosmos

  • 相关阅读:
    当spark遇见hbase
    激活函数
    scala的插值器
    sql语句基础
    机器学习中的归一化方法
    sklearn的使用
    sonarqube报错Cause: java.sql.BatchUpdateException: Incorrect string value: 'xF2xB4xB0xBFxDAxBE...' for column 'message' at row 38
    soanrqube 语言插件下载
    sonarqube扫描报错:Caused by: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (4522715 > 4194304). You can change this value on the server by setting the max_allowed_packet' variable.
    xcode11 xcrun altool 自动上传ipa到app store 报错A Distribution Provisioning profile should be used when submitting apps to the App Store. For more information
  • 原文地址:https://www.cnblogs.com/tuapu/p/15382848.html
Copyright © 2020-2023  润新知