概述
模板元编程可以说是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>::value
或one_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_sequence
是std::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被推导为int
,const int
和const 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;
朽化的具体步骤
对于最终的结果type
,std::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 thatstd::add_lvalue_reference<void>::type
is void, whilevoid&
leads to a compilation error.
更多类型修改
更多内容可以查看文档: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,替代失败不代表是个错误
-
error,在这里指编译错误,当出现编译错误的时候会停止代码的链接等后续活动。如上述例子中判断偶数的模板函数中,若函数参数非整形类型,那么会出现error
std::cout << std::boolalpha << is_odd(20.0) << std::endl;
-
failure,当失败发生时不代表编译出错,当我们使用
switch
,if-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-else
,for
,while
这些运行期语句。但是我们可以通过std::enable_if
,std::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
的,不信的话读者可以自行尝试一下