Loading

C++中的模板元编程

概述

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

模板元编程:template meta programing

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

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

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;
}

手撕环节

实现函数类型萃取类

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...>>;
    };
    
    template<std::size_t I>
    using args_t = typename args<I>::ParamType;
};

// 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;
    // int
    PRINT_TYPEID(function_traits<decltype(f)>::ReturnType);
    // 编译出错 "args index out of range"
    PRINT_TYPEID(function_traits<decltype(f)>::args_t<1>);
    // 编译出错 "this is just a build in type"
    PRINT_TYPEID(function_traits<int>);
}

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

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)

实现my_any

  • my_any中记录的数据是动态变换的,因此对my_any中记录的数据进行类型判断需要使用到RTTI,即dynamic_cast,而动态类型检查依赖于多态环境
  • my_any的通用引用构造函数中使用SFINAE来避免通用引用与拷贝等特种函数发生参数抢夺的问题
  • my_any本身并非是一个模板类,它的类型固定是my_any,它的内部维护了一个多态指针,指向多态指针的派生模板类
struct base_container {
    virtual ~base_container() = default;
    virtual base_container* clone() const = 0;
};

template<typename T>
struct any_container : base_container {
    T data;

    template<typename U>
    any_container(U&& _data) : data(std::forward<U>(_data)) {}

    base_container* clone() const override {
        return new any_container<T>(data);
    }
};

class my_any final {
private:
    base_container* pContainer;

public:
    template<typename T, typename U = std::enable_if_t<!std::is_same_v<my_any, std::decay_t<T>>, void>>
    my_any(T&& param) : pContainer(new any_container<std::decay_t<T>>(std::forward<std::decay_t<T>>(param))) {}

    ~my_any() { delete pContainer; }

    my_any(const my_any&& _another) : pContainer(_another.pContainer->clone()) {}

    my_any& operator=(const my_any& _another) {
        if (_another.pContainer == pContainer)
            return *this;
        delete pContainer;
        pContainer = _another.pContainer->clone();
        return *this;
    }

    my_any(my_any&& _another) : pContainer(_another.pContainer) { _another.pContainer = nullptr; }

    my_any& operator=(my_any&& _another) {
        if (_another.pContainer == pContainer)
            return *this;
        delete pContainer;
        pContainer = _another.pContainer;
        _another.pContainer = nullptr;
        return *this;
    }

    template<typename T>
    inline bool is_type() {
        return (dynamic_cast<any_container<std::decay_t<T>>*>(pContainer) != nullptr);
    }

    template<typename T>
    inline T& as_type() {
        if (auto pAnyContainer = dynamic_cast<any_container<std::decay_t<T>>*>(pContainer); pAnyContainer == nullptr)
            throw std::bad_cast{};
        else
            return pAnyContainer->data;
    }
};

实现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;
}

更多

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
}

模板推导类型的舍弃

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();
}

模板的底层实现是什么

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

学习资料

https://blog.csdn.net/jeffasd/article/details/84667090

https://ouuan.github.io/post/c-11-enable-if-的使用/

https://www.zhihu.com/column/c_1306966457508118528

https://zhuanlan.zhihu.com/p/378355217

https://www.cnblogs.com/qicosmos/p/4480460.html

posted @ 2021-10-08 20:55  _FeiFei  阅读(1292)  评论(2编辑  收藏  举报