模版元入门
模版类型推导
Section titled “模版类型推导”template<typename T>void f(ParamType param);
f(expr)对上面这种形式,依据ParamType可以分为三种情况
- ParamType是指针或引用类型,但不是通用引用(Universal Reference)【通用引用应该是modern effective c++的作者提出来的概念,表示模版中&&针对传入的是左值还是右值提供不同的效果,本质cpp只有引用折叠】
- ParamType是通用引用(Universal Reference)
- ParamType既不是指针也不是引用
ParamType是指针或普通引用类型
Section titled “ParamType是指针或普通引用类型”/// 引用template<typename T>void f(T& param);
int x = 27;const int cx = x;const int& rx = x;
f(x); // T is int, ParamType is int&f(cx); // T is const int, ParamType is const int&f(rx); // T is const int, ParamType is const int&const修饰会跟随传递进模版参数T;但是引用修饰会被忽略(指针与引用类似)
template<typename T>void f(const T& param);
int x = 27;const int cx = x;const int& rx = x;
f(x); // T is int, ParamType is const int&f(cx); // T is int, ParamType is const int&f(rx); // T is int, ParamType is const int&ParamType是通用引用
Section titled “ParamType是通用引用”templat<typename T>void f(T&& param);
int x = 27;const int cx = x;const int& rx = x;
f(x); // x is lvalue, T is int&, ParamType is int&f(cx); // x is lvalue, T is const int&, ParamType is const int&f(rx); // x is lvalue, T is const int&, ParamType is const int&f(27); // x is rvalue, T is int, ParamType is int&&如果传入f的是个左值,T和ParamType都会被推导为左值引用
如果传入f的是右值, 规则同case1
引用折叠?&传给 && 或者 && 传给& 都会变成&; && 传给&&还是&&
ParamType不是引用/指针
Section titled “ParamType不是引用/指针”templat<typename T>void f(T param);
int x = 27;const int cx = x;const int& rx = x;int* y = x;
f(x); // T is int, ParamType is intf(cx); // T is int, ParamType is intf(rx); // T is int, ParamType is intf(y); // T is int*, ParamType is int*传引用,值传递,函数中的T相当于是个深拷贝,因此T不会带const
传指针进去会解析成指针类型
struct A {int x = 0;};
template<typename T>void f1(T a){ a.x = 1;}
template<typename T>void f2(T& a){ a.x = 2;}
int main() { A a; A& a1 = a; A& a2 = a;
// auto a11 = a; auto = A, a1 is A f1(a1); std::cout << a1.x << std::endl;
// auto& a22 = a; auto = A, a22 is A& f2(a2); std::cout << a2.x << std::endl;}auto类型推导
Section titled “auto类型推导”auto type deduction is template type deduction
// auto x = 27; 等价于template<typename T>void f(T param);f(27); // auto = T = int
// const auto cx = x; 等价于template<typename T>void f(const T param);f(cx); // auto = T = int
// const auto& rx = x;template<typename T>void f(const T& param);f(rx); // auto = T = int
// auto&& y = std::move(x)template<typename T>void f(T&& param);f(y); // auto = T = int, ParamType = int&&为什么下面两个都可以
int main() { auto a1 = new A; // case 3 delete a1;
auto* a2 = new A; // case 1 delete a2;}模版参数主要有三种
- 类型
- 整数和整数的衍生类型
- 模版
类型模版参数
Section titled “类型模版参数”template<typename T>void print(const T& t){ std::cout << t << std::endl;}
print(1);int x = 1;print(x);必须编译期确定的值
template <int* p>void f(int data){ *p = data;}
int g = 1;
struct A{ int m1; static int m2;};
int A::m2 = 1;
int main(){ f<&g>(0); f<&A::m2>(0);
static int c = 3; f<&A::m2>(0);}整数衍生类型
地址在编译期肯定不知道,但是只要保证某个地址的数据在运行期间是不变的,就可以用这种方式
template <int* p>void f(int data){ *p = data;}函数指针
因为函数地址在程序段,执行过程就不会变,所以可以用作模版参数
//// Created by ChenhuiWang on 2024/3/28.
// Copyright (c) 2024 Tencent. All rights reserved.//#include "iostream"#include <vector>
template <void (*func)()>void f(){ func();}
void f1(){}
struct A{ void f2(){} static void f3(){}};
int main(){ constexpr void (*pf1)() = &f1;
f<&f1>(); f<pf1>(); f<&A::f2>(); //error: &A::f2的类型是void (Test::*)() f<&A::f3>();}模版类型(模版嵌套)
Section titled “模版类型(模版嵌套)”template <template<typename, typename> typename Tem>void f(){ Tem<int, std::string> te; te.show();}
template<typename T1, typename T2>struct A1{ void show(){ std::cout << 1 << std::endl; }};
template<typename T1, typename T2>struct A2{ void show(){ std::cout << 2 << std::endl; }};
int main(){ f<A1>(); //A1实例化前在是template类型 f<A2>();}template<typename... Ts>auto sum(Ts... ts){ return (ts + ...);}
int main(){ sum(1, 2); sum(1.0, 2);}typename… 表示包pack, 包名为Ts, Ts… 表示对Ts进行解包, 必须在参数列表的最后。
Ts可以为空
- 直接展开,包会被编译器展开为逗号间隔的参数
std::byte memory[16384];void* head = memory;
template<typename T, typename... Args>T& create(Args... args){ auto& obj = *new(head) T(args...); // use operator new head += sizeof(T); return obj;}
struct A{ A(int, double){}}
int main(){ auto a = Create<A, int, double>(1, 2.5);}
// 模版实例化后会这样A& create(int arg1, double arg2){ auto& obj = *new(head) A(arg1, arg2); head += sizeof(T); return obj;}- 嵌套展开
template<typename T, int... N>std::vector<T> GetSubVector(const std::vector<T>& src){ return std::vector<T>{src.at(N)...}}- 按照符号展开(c++ 17)折叠表达式
// c++17之前template<typename T>auto sum(T arg){ return arg;}
template<typename T, typename... Ts>auto sum(T t, Ts... ts){ return t + sum<Ts...>(ts);}
sum<int, double, float, short>(1, 1.0, 2.0, 3);==> 1 + sum<double, float, short>(1.0, 2.0, 3);==> 1 + 1.0 + sum<float, short>(2.0, 3);==> 1 + 1.0 + 2.0 + sum<short>(3);==> 1 + 1.0 + 2.0 + 3
// c++17 之后template<typename T>auto sum(T... ts){ // 三个点在左边, 表示从左到右合并 // 折叠表达式必须要有括号 return (... + ts);}写在header中的原因
Section titled “写在header中的原因”template<typename T>void f();
// a.cpp// 如果把这个写进a.h就不会报错了,并且如果a.h被多文件include,不会产生multi defined// 因为未实例化的模版是自动inline的// 注意全特化的话就不会自动inline了,他是实例化后的模版函数template<typename T>void f(){
}
// main.cpp#include <a.h>int main(){ f<int>();}产生两个编译单元 main.o和a.o
main.o中void f<int>(); 去其他地方找实现,但是a.o中没有使用f<int>就不会产生相应的函数被链接了。
模版显示实例化
Section titled “模版显示实例化”template<typename T>void f();
// a.cpptemplate<typename T>void f(){}
// 显式实例化template f<int>();
// main.cpp#include <a.h>int main(){ f<int>();}SFINAE
Section titled “SFINAE”Substitution Failure Is Not An Error
//// Created by ChenhuiWang on 2024/3/29.
// Copyright (c) 2024 Tencent. All rights reserved.//#include <iostream>
template<typename T, bool Condition>struct EnableIf{};
template<typename T>struct EnableIf<T, true>{ using type = T;};
template<typename T>void f(typename EnableIf<T, sizeof(T) <= sizeof(void*)>::type t){ std::cout << "1" << std::endl;}
template<typename T>void f(typename EnableIf<T, (sizeof(T) > sizeof(void*))>::type t){ std::cout << "2" << std::endl;}
struct A{ int data[16];};void Demo(){ f<int>(1); A a; f<A>(a);}
//实例化过程,进行模版匹配f<int>(1) ==> void f(typename EnableIf<T, sizeof(T) <= sizeof(void*)>::type t) ==> 命中EnableIf的EnableIf<T, true>的特化版本 ==> f(int)
f<A>(a) ==> void f(typename EnableIf<T, sizeof(T) <= sizeof(void*)>::type t) ==> 没有这个特化版本,回归到通用模版template<typename T, bool Condition> struct EnableIf{}; ==> 通用版本没有::type, 匹配失败 ==> 继续匹配 void f(typename EnableIf<T, sizeof(T) > sizeof(void*)>::type t) ==> 命中EnableIf的EnableIf<T, true>的特化版本 ==> f(A)这里的enable_if是std::enable_if的简化版。
SFINAE是模版元编程最重要的理论基础,核心在于【构造一种模式,让其匹配成功或失败】
模版元就是静态计算+类型处理
template<int N>struct Fib{ constexpr static int value = Fib<N - 2>::value + Fib<N - 1>::value;};
template<>struct Fib<0>{ constexpr static int value = 1;};
template<>struct Fib<1>{ constexpr static int value = 1;};
int main(){ int a[Fib<10>::value];}更多的是对类型进行判断
template<typename T1, typename T2>struct is_same{ constexpr static bool value = false;};
template<typename T>struct is_same<T, T> { constexpr static bool value = true;};
template<typename T>typename std::enable_if<is_same<T, int>::value, void>::typef(T t){}写起来比较啰嗦,所以更多是这样
template<typename T, T val>struct integer_const{ constexpr static T value = val;}
template<bool val>using bool_constant = integer_constant<bool, val>;类型处理(萃取)【type traits】
Section titled “类型处理(萃取)【type traits】”std::conditional
template<bool cond, typename T1, typename T2>struct conditional { using type = T1;};
template<typename T1, typename T2>struct conditional<false, T1, T2>{ using type = T2;};
template <typename T>void f(typename conditional<std::is_fundamental<T>::value, T, const T&>::type t){
}
void Demo(){ int a = 0; std::string str = "abc"; f<int>(a); f<std::string>(str);}template <bool cond, typename T1, typename T2>using conditional_t = typename conditional<cond, T1, T2>::type;
template <typename T>void f(conditional_t<std::is_fundamental<T>::value, T, const T&> t){
}实现一个动态的get
Section titled “实现一个动态的get”// std::get返回值类型在编译期就是确定的std::tuple<int, std::string, float> tp = {1, "1", 1.0};std::cout << std::get<0>(tp);
// 如何实现一个动态的get, 比如get(tp, i)//// Created by ChenhuiWang on 2024/4/11.
// Copyright (c) 2024 Tencent. All rights reserved.//#include <tuple>#include <string>#include <iostream>
struct Base { virtual void f() const = 0;};
struct D1 : public Base { void f() const override { }};
struct D2 : public Base { void f() const override { }};
template<typename... Args>std::enable_if_t< std::conjunction_v< std::is_base_of<Base, Args>... >, void>Invoke(const std::tuple<Args...> &tup, size_t index) {
}
int main() { std::tuple tu{D1(), D2(), D1()}; Invoke(tu, 1);}然而Invoke函数里index是运行期变量,不能使用std::get<index>,所以采用编译期展开的方式
struct Base { virtual void f() const = 0;};
struct D1 : public Base { void f() const override { std::cout << "1\n"; }};
struct D2 : public Base { void f() const override { std::cout << "2\n"; }};
template<int N, typename... Args>void TryInvoke(const std::tuple<Args...>& tup, size_t index){ if constexpr (N < sizeof...(Args)){ if(index == N){ std::get<N>(tup).f(); return; }
TryInvoke<N + 1, Args...>(tup, index); }}
template<typename... Args>std::enable_if_t< std::conjunction_v< std::is_base_of<Base, Args>... >, void>Invoke(const std::tuple<Args...> &tup, size_t index) { if(index >= sizeof...(Args)){ return; } TryInvoke<0, Args...>(tup, index);}
int main() { std::tuple tu{D1(), D2(), D1()}; Invoke(tu, 0); Invoke(tu, 1);}这里用到了模版实例化的递归,在编译期递归生成多个模版函数
实现一个std::invoke
Section titled “实现一个std::invoke”template<typename T, typename... Args>auto invoke(T&& obj, Args&&... args){ return obj(std::forward<Args>(args)...);}
void f(int a){ std::cout << a;}
int main(){ invoke(f, 4);}实现一个std::apply
Section titled “实现一个std::apply”template<typename T, typename... Args>auto invoke(T&& obj, Args&&... args){ return obj(std::forward<Args>(args)...);}
template<typename T, typename Tup, size_t... Index>decltype(auto) my_apply(T&& obj, Tup&& tup){ return invoke(obj, std::get<Index>(std::forward<Tup>(tup))...);}
void f(int a, int b){ std::cout << a << " " << b;}
int main(){ invoke(f, 4, 5); std::tuple<int, char>tup = {1, 'a'}; my_apply<void(*)(int, int), std::tuple<int, char>, 0, 1>(f, std::move(tup));}调用太啰嗦了,需要一个工具自动生成从0到N-1的序列
template<int N, int... Index>struct A : public A<N - 1, N - 1, Index...> {
};
template<int... Index>struct A<0, Index...> { static void printIndex() { ((std::cout << Index << " "), ...); }};
int main() { A<5>::printIndex();}template <size_t... Index>struct sequence { static void printIndex() { ((std::cout << Index << " "), ...) << std::endl;
}};
template<size_t N, size_t... Index>struct make_sequence : make_sequence<N - 1, N - 1, Index...> {};
template <size_t... Index>struct make_sequence<0, Index...> { using result = sequence<Index...>;};
template<typename T, typename... Args>auto invoke(T&& obj, Args&&... args) { return obj(std::forward<Args>(args)...);}
template<typename T, typename Tup, size_t... Index>auto apply_impl(T&& obj, Tup&& tup, sequence<Index...>) { return invoke(std::forward<T>(obj), std::get<Index>(std::forward<Tup>(tup))...);}
template<typename T, typename Tup>auto my_apply(T&& obj, Tup&& tup) { make_sequence<std::tuple_size_v<std::decay_t<Tup>>>::result::printIndex(); typename make_sequence<std::tuple_size_v<std::decay_t<Tup>>>::result res; return apply_impl(std::forward<T>(obj), std::forward<Tup>(tup), res);}
void f(int a, double b, char c) { std::cout << a << " " << b << " " << c ;}
int main() { std::tuple tu{1, 1.4, 'a'}; my_apply(f, tu); return 0;}实现一个variant
Section titled “实现一个variant”std::variant用法
struct A{ A() = default;};int main(){ std::variant<A, int, char, std::string> var; var = 1; std::cout << var.index();}可以存放多种类型但同一时刻只能存放一种
template<typename T1, typename T2>class variant{public: variant(const T1& t1){ data.t1 = t1; }; variant(const T2& t2){ data.t2 = t2; };
private: union{ T1 t1; T2 t2; }data; int index;};对于任意参数的variant无法映射到union中
本来variant的目的就是复用内存,因此我们可以自己管理一块内存
template<typename... Ts>class variant{public: template<int Index, typename... Args> variant(Args&&... args){
}private: void* data = std::malloc(std::max(sizeof(Ts)...)); int index;};
int main(){ variant<1, int> va; //无法正确实例化模版类的模版构造函数}然而模版类的模版构造函数是不能实例化的,因为会被识别成类模版参数,因此构造函数只能依赖于自动推导
Index怎么推导呢 ==> 使用一个in_place模版类
template<size_t Index>struct in_place_index_t{ explicit in_place_index_t() = default;};
template<size_t Index>inline constexpr in_place_index_t<Index> in_place_index{}
template<typename... Ts>class variant{public: template<size_t Index, typename... Args> variant(const in_place_index_t<Index>& index, Args&&... args){
}private: void* data = std::malloc(std::max(sizeof(Ts)...)); int index;};
int main(){ variant<int, char> var_int{in_place_index<0>(), 1}; variant<int, char> var_char{in_place_index<1>(), 'a'};}实现构造函数需要,new 创建相应类型的对象,就需要从静态的模版参数size_t Index到具体类型的工具, 额外实现了一个get方法
template<size_t Index, typename Head, typename... Types>struct get_type_by_index : get_type_by_index<Index - 1, Types...>{};
template<typename Head, typename... Types>struct get_type_by_index<0, Head, Types...>{ using type = Head;};
template<typename... Ts>class variant{public: template<size_t Index, typename... Args> variant(const in_place_index_t<Index>& index, Args&&... args){ using data_type = std::decay_t<typename get_type_by_index<Index, Ts...>::type>; mData = new data_type(args...); mIndex = Index; }
int index() const { return index; }
template<size_t Index> auto get() const { using data_type = std::decay_t<typename get_type_by_index<Index, Ts...>::type>; return *static_cast<data_type*>(mData); }
private: void* mData = std::malloc(std::max(sizeof(Ts)...)); int mIndex;};而析构、拷贝构造、拷贝赋值需要从运行时的mIndex成员变量到具体类型,采用静态穷举所有可能情况的方式
template<typename... Ts>class variant {private: template<typename Type> void destory_data() { delete static_cast<std::add_pointer_t<Type>>(mData); }
template<typename Type> void create_data(const void* other_data){ new(mData) Type(*static_cast<const Type*>(other_data)); }
public: ~variant() { std::array<void (variant<Ts...>::*)(), sizeof...(Ts)> destroy_functions = { &variant<Ts...>::destory_data<Ts>... }; if(mData != nullptr){ (this->*destroy_functions.at(mIndex))(); } }
variant(const variant& other){ std::array<void(variant::*)(const void*), sizeof...(Ts)> create_functions = { &variant::create_data<Ts>... }; mIndex = other.mIndex; (this->*create_functions.at(mIndex))(other.mData); }
variant(variant&& other){ mData = other.mData; mIndex = other.mIndex; other.mData = nullptr; other.mIndex = -1; }
variant& operator=(const variant& other){ if(this == &other) return *this; // release data std::array<void (variant::*)(), sizeof...(Ts)> destroy_functions = { &variant::destory_data<Ts>... }; (this->*destroy_functions.at(mIndex))();
// copy assignment std::array<void(variant::*)(const void*), sizeof...(Ts)> create_functions = { &variant::create_data<Ts>... }; mIndex = other.mIndex; (this->*create_functions.at(mIndex))(other.mData);
return *this; }
variant& operator=(variant&& other){ if(this == &other) return *this;
// release data std::array<void (variant::*)(), sizeof...(Ts)> destroy_functions = { &variant::destory_data<Ts>... }; (this->*destroy_functions.at(mIndex))();
// move assignment mData = other.mData; mIndex = other.mIndex; other.mData = nullptr; other.mIndex = -1;
return *this; }
}然后想实现variant<int, char> var(1); 而不通过in_place_index来构造,进而可以支持implicit转化, variant<int, char> var = 1
需要一个工具从type ==> index
template <typename Target, typename Head, typename... Ts>struct get_index_from_types { constexpr static size_t value = get_index_from_types<Target, Ts...>::value + 1;};
template <typename Target, typename... Ts>struct get_index_from_types<Target, Target, Ts...>{ constexpr static size_t value = 0;};
std::cout << get_index_from_types<Test1, int, char, std::string, Test1>::value;
template<typename T> variant(T&& t){ constexpr size_t index = get_index_from_types<T, Ts...>::value; new(mData) T(std::forward<T>(t)); mIndex = index;}
template<typename Type> auto get() const { constexpr size_t index = get_index_from_types<Type, Ts...>::value; return get<index>(); }完整代码
//// Created by ChenhuiWang on 2024/4/3.
// Copyright (c) 2024 Tencent. All rights reserved.//#include <iostream>#include <array>
template<size_t Index>struct in_place_index_t { explicit in_place_index_t() = default;};
template<size_t Index>inline constexpr in_place_index_t<Index> in_place_index{};
template<size_t Index, typename Head, typename... Types>struct get_type_by_index : get_type_by_index<Index - 1, Types...> {};
template<typename Head, typename... Types>struct get_type_by_index<0, Head, Types...> { using type = Head;};
template <typename Target, typename Head, typename... Ts>struct get_index_from_types { constexpr static size_t value = get_index_from_types<Target, Ts...>::value + 1;};
template <typename Target, typename... Ts>struct get_index_from_types<Target, Target, Ts...>{ constexpr static size_t value = 0;};
template<typename... Ts>class variant {public: template<size_t Index, typename... Args> variant(const in_place_index_t<Index> &index, Args &&... args) { using data_type = std::decay_t<typename get_type_by_index<Index, Ts...>::type>; new(mData) data_type(args...); mIndex = Index; }
template<typename T> variant(T&& t){ constexpr size_t index = get_index_from_types<T, Ts...>::value; new(mData) T(std::forward<T>(t)); mIndex = index; }
~variant() { std::array<void (variant::*)(), sizeof...(Ts)> destroy_functions = { &variant::destory_data<Ts>... }; if(mData != nullptr){ (this->*destroy_functions.at(mIndex))(); } }
variant(const variant& other){ std::array<void(variant::*)(const void*), sizeof...(Ts)> create_functions = { &variant::create_data<Ts>... }; mIndex = other.mIndex; (this->*create_functions.at(mIndex))(other.mData); }
variant(variant&& other){ mData = other.mData; mIndex = other.mIndex; other.mData = nullptr; other.mIndex = -1; }
variant& operator=(const variant& other){ if(this == &other) return *this; // release data std::array<void (variant::*)(), sizeof...(Ts)> destroy_functions = { &variant::destory_data<Ts>... }; (this->*destroy_functions.at(mIndex))();
// copy assignment std::array<void(variant::*)(const void*), sizeof...(Ts)> create_functions = { &variant::create_data<Ts>... }; mIndex = other.mIndex; (this->*create_functions.at(mIndex))(other.mData);
return *this; }
variant& operator=(variant&& other){ if(this == &other) return *this;
// release data std::array<void (variant::*)(), sizeof...(Ts)> destroy_functions = { &variant::destory_data<Ts>... }; (this->*destroy_functions.at(mIndex))();
// move assignment mData = other.mData; mIndex = other.mIndex; other.mData = nullptr; other.mIndex = -1;
return *this; }
int index() const { return index; }
template<size_t Index> auto get() const { assert(Index == mIndex); using data_type = std::decay_t<typename get_type_by_index<Index, Ts...>::type>; return *static_cast<data_type *>(mData); }
template<typename Type> auto get() const { constexpr size_t index = get_index_from_types<Type, Ts...>::value; return get<index>(); }
private: void *mData = std::malloc(std::max(sizeof(Ts)...)); int mIndex;
template<typename Type> void destory_data() { delete static_cast<Type*>(mData); }
template<typename Type> void create_data(const void* other_data){ new(mData) Type(*static_cast<const Type*>(other_data)); }};
struct Test1{ ~Test1(){ std::cout << "~Test1()" << std::endl; }};struct Test2{ ~Test2(){ std::cout << "~Test2()" << std::endl; }};
int main() { variant<Test1, Test2> var1(in_place_index<0>);// variant<Test1, Test2> var2(var1);//// std::variant<short, int> v = 1;// std::cout << v.index();// std::cout << get_index_from_types<int, int>::value;//// std::cout << get_index_from_types<Test1, int, char, std::string, Test1>::value;
struct A{ A(int m, char n) : m(m), n(n) {} int m; char n; };
// std::variant<int, A> v(A{1, 'c'});// v = A{2, 'a'};
variant<int, A> var3(A{1, 'c'}); var3 = A{2, 'a'}; std::cout << var3.get<A>().m; std::cout << var3.get<0>();
std::variant<int, char> a(std::in_place_index<0>, 3);}