简介
元编程是更高层次的抽象,对代码进行编程。把程序甚至自己作为输入数据来处理,比如py的虚拟机就是元程序,它处理py的代码把其转换成py的虚拟机指令。称py为输入语言domain language,称输出的语言字节码为host language
根据是否使用了domain-language将元编程分为2类
- 使用domain language的meta programming,普通程序和元程序是分离的
- 直接使用host language来实现元编程,通常通过编译机制,将元编程代码生成逻辑代码,然后和其他的逻辑代码合并在一起,看起来像“逻辑代码自己改写自己”,c++的TMP属于这种。
c++模板元编程
模板通过编译器生成其他c++代码,然后和其他的c++代码一起编译
template<int N>
struct binary {
//static constexpr int value = binary<N/10>::value << 1 | N % 10;
enum {
value = binary<N/10>::value << 1 | N % 10,
};
};
template<>
struct binary<0> {
enum {
value = 0,
};
};
//证明编译期代码1
static char array[binary<101>::value];
//证明编译期代码2
template<int N>
struct A{};
A<binary<101>::value> a;
小技巧c++中 如果包含static关键字的东西,大概率是编译期确定的,反正dynamic关键字通常是运行时确定的,比如dynamic_cast,dynamic
c++模板已经被证明是图灵完备的了,意味着通过计算机表达的任何计算都可以用tmp来实现
学习tmp的原因
可以编译期计算,提升运行时效率,通常会导致编译期时间变长,以及编译出的文件大小变大
-
可以针对类型做计算,由于c++缺少自省机制
if(typeof(var) == int){}else{} //psedo code
-
tmp可以让代码变得简单优美
py::class_<Student>("m","Student") .def (py:init<const std::string&>()) .def ("set_name",&Student::set_name)
tmp应用比较广泛且是未来趋势,比如asio,比如boost
模板基本概念
可以声明5中不同的模板
- template<typename T> struct class_tmpl;
- template<typename T> void func_tmpl(T );
- template<typename T> var_tmpl;//变量模板 c++ 14
- template<typename T> using alias_tmpl = T; //别名模板 c++11
- template<typename T> concept no_constrant = true; //c++ 20
前3个模板都可以用于定义,后两个不需要定义,不产生运行时的实体
- template<typename T> struct class_tmpl{}
- template<typename T> void func_tmpl(T arg){}
- template<typename T> var_tmpl = T(3.14);
c++14中有范型lambda,但是不是模板,是普通的lambda,但是它的operator()函数是个模板
auto glambda = []<typename T>(T a,auto && b){return a<b;}
模板形参和实参
template<int n> struct NoneTypeTemplateParameter {}; //非类型形参
template<typename T> struct TypeTemplateParameter {}; //类型模板形参
template<template<typename T> typename tmpl> struct TemplateTemplateParameter {}; //模板模板形参
//应用
NoneTypeTemplateParameter<1>();
TypeTemplateParameter<int>();
TemplateTemplateParameter<std::hash>();
非类型模板形参只接受“结构化类型常量”
- 整形 int char,long等
- enum类型
- 指针和左值引用
- 浮点类型 以及 字面常量 literal class type (c++ 20 后才支持)
template<float& f> void foo() {std::cout<<f<<std::endl;}
static float f1 = 0.1f;
float f2 = 0.2f;
foo<f1>(); // 0.1f
foo<f2>(); // error 模板实参需要编译期可以推导
模板模板形参 只接受 类模板 或者类的别名模板作为实参,不接受函数模板,且形参和实参的模板参数匹配
template <template <typename T> typename Tmpl>
struct S {};
template<typename T> void foo(){};
template<typename T> struct Bar1{};
template<typename T,typename U> struct Bar2{};
S<foo>(); //error
S<Bar1>();//ok
S<Bar2>();//error 形参和实参的模板参数不匹配
变参形参列表 可以接受多个或者0个参数,参数可以是非类型的常量,可以是类型,也可以是模板。 变长的部分一定要在参数的最后
template<int... Args> struct VariadicTemplate1 {};
template<int, typename... Args> struct VariadicTemplate2 {};
template<template <typename T> typename... Args> struct VariadicTemplate3 {};
VariadicTemplate1<1,2,3>();
VariadicTemplate2<int,1>();
VariadicTemplate3<>();
VariadicTemplate3<std::hash,std::hash>();
//可以设置默认模板参数
templat<typename T = int> struct WithDefaultTemplate {};
模板的实例化,指的模板生成具体函数或者变量的过程
Implicit 实例化
//t.hpp
t
//t.cpp
//按需实例化,隐式实例化
#include "t.hpp"
foo<int>(1);
foo(2);
std::function<void(int)> func = &foo(int);
//显式实例化 告诉编译器去实例化这个模板 但是现在不用
tempate void foo<int>(int);
tempate void foo<>(int);
tempate void foo(int);
使用模板时,编译器根据实参生成对应的代码,插入个特定的位置,然后把代码插入,这个位置称为POI(point of instantiation),所以在同一个编译单元里,编译器一定要能看到模板的定义,最常见的做法,模板定义放在头文件,然后在cpp文件include 这个头文件来获取模板的定义,这个也被称为包含模型。
包含模型的问题,
- 不同编译单元,相同模板会被实例化多次,从而产生多个相同的类型,函数或变量,会导致链接时重定义的问题
- 浪费编译时间
//主流解决方案式对模板生成的对象,添加个特殊标记,链接器在链接时,对含有这些特殊标记的符号做特殊处理。比如gnu下模板生成的符号都会被标记成弱符号
//或者显式实例化声明 c++ 11之后
extern tempate void foo<int>(int);
extern tempate void foo<>(int);
extern tempate void foo(int);
模板实参推导
template<typename T>
void foo(T,T) {};
foo(1,2);//#1 T deduced 1 等于foo<int>
foo(1,2.0);//1st arg deduced int,2st deduce double ,推导失败
c++17后 可以通过类模板的构造函数推导类模板
template <typename T>
struct S {S(T,int){};};
S s(1,2); //T deduced int
std::pair<int,float>(1,2.0f); //before c++17
std::make_pair(1,2.0f);//before c++17
std::pair(1,2.0f);//since c++17
模板特化
模板特化允许替换部分或者全部形参,并且定义个对应替换的模板实现,定义一个替换全部的形参的特化称为全特化,定一个替换部分形参的特化称为偏特化,非特化的原始模板称为主模板
template<typename T,typename U> void foo(T,U){} //主模板
template<> void(int,float) {} //全特化
//需要注意的是函数模板不能偏特化,只有类模板和变量模板才可以,虽然不能偏特化,但是可以通过函数的重载实现类似的效果
template<typename T,typename U> struct S{} //#1 主模板
template<typename T> struct S<int,T>{} //#2 偏特化
template<> struct S<int,float>{} //#3 全特化
//模板实例化的时候,编译器会从所有的特化版本中选择最匹配的特化版本来做替换,如果没有任意特化进行匹配,就会选择主模板来匹配
S<int,int>(); #2
S<int,float>(); #3
S<float,int>(); #1
//注意不要混淆模板的全特化和模板显式实例化 长得非常像,区别仅仅多了个<>
template void fun(int,int); //模板显式实例化
template<> void fun(int,int).; //模板全特化
//模板的实例化的结果就应该是个全特化,有时候称模板生成的结果为隐式特化,本节中讨论的机制为显示特化。所以不同环境下“specialization” 未必就指的本节讨论的特化
函数模板重载
函数模板可以重载,并且可以和普通的函数一起重载,使用重载可以实现和偏特化类似的效果。c++模板或者函数只要签名不同,就可以重载。
template<typename T> void foo(T) {};#1
template<typename T> void foo(T*) {};#2
template<typename T> int foo(T) {};#3
template<typename T,typename U> void foo(T) {};#4
template<typename T,typename U> void foo(T,U) {};#5
template<typename U,typename T> void foo(T,U) {};#6
void foo(int) {}; #7
void foo(float,int) {}; #8
//由于有模板实参推导机制,所以函数模板可以和普通函数一起重载
//call
foo(1); //#7
foo(new int(1));//#2
foo(1.0f,1); //#8
foo<int,float>(1,1.0f);//#5
foo(1.0,1);//errro ambiguous
TMP特点
- 使用模板当成函数(metafunction),模板参数当做函数参数,static 成员当作函数返回值
- 使用模板实例化来调用metafunction
- 使用特化或者重载来实现分支的选择
- 使用递归来实现循环
- 由编译器驱动
- 处理的数据是常量 或者 类型
TMP运用
例子1 is_reference
std::cout<<std::is_reference<int>::value<<std::endl;
std::cout<<std::is_reference<int&>::value<<std::endl;
std::cout<<std::is_reference<int&&>::value<<std::endl;
//实现
template<typename T> struct IS_Reference{ static constexpr bool value = false; };
template<typename T> struct IS_Reference<T&>{ static constexpr bool value = true; };
template<typename T> struct IS_Reference<T&&>{ static constexpr bool value = true;};
//编译器怎么判断一个特化能不能匹配的呢,重新记录下参数名称
template<typename T1> struct IS_Reference{ static constexpr bool value = false; };
template<typename T2> struct IS_Reference<T2&>{ static constexpr bool value = true; };
template<typename T3> struct IS_Reference<T3&&>{ static constexpr bool value = true;};
//对于
IS_Reference<int&>::value 这个实例化
带入1号模板 T1 = int&
带入2号模板 T1 = T2& = int& 推导出 T2 = int ok
带入3号模板 T1 = T2&& = int& 推导失败
//判断特化能不能匹配
1.先确定所有的模板实参
2.带入到特化中
3.反向推导特化的形参
remove_reference
template<typename T> void foo(T a){a = a +1;}
int i = 0;
foo<int&>(i); //pass lvalue ref
foo<int&&>(1);//pass rvalue ref
//但是我们希望按值去传递 最好去除引用
//Remove_reference实现
template<typename T> struct Remove_reference{ using type = T; };
template<typename T> struct Remove_reference<T&>{ using type = T; };
template<typename T> struct Remove_reference<T&&>{ using type = T; };
//之所以要加typename 编译器处理这句时,无法知道这个是个类型,所以需要显式的制定typename
template<typename T> void foo(typename Remove_reference<T>::type a){ a = a +1;}
metafunction 规定的概念
接收常量和类型作为参数,返回类型和常量当结果
- metadata
- 类型和常量
- 常量被模板包起来被称为“none-type metadata”
- metafunciton
- 是编译期间的“函数”,接收metadata作为参数,并且返回metadata作为结果
//type metadata
int;
struct S{};
//none-type metadata
template<bool b> struct bool_ {static constexpr bool value = b;}
//metafunciton
template<typename T> struct is_reference{using type = bool_<false>;};
template<typename T> struct is_reference<T&>{using type = bool_<true>;};
template<typename T> struct is_reference<T&&>{using type = bool_<true>;};
//典型的none-type metadata
template<typename T, T v> struct intergral_constant {
static constexpr T value = v;
using value_type = T;
using type = intergral_constant;
constexpr operator T() const noexcept {return value;}
constexpr T operator()() const noexcept {return value;}
};
//alias
template <bool B> using bool_constant = intergral_constant<bool,B>;
using true_type = bool_constant<true>;
using false_type = bool_constant<false>;
所以is_reference实现可以变成
template<typename T> struct ISReference{ using type = false_type; };
template<typename T> struct ISReference<T&>{ using type = true_type; };
template<typename T> struct ISReference<T&&>{ using type = true_type;};
std::cout<<ISReference<int>::type::value;
std::cout<<ISReference<int>::type();
std::cout<<ISReference<int>::type()();
public继承是个比较容易的方式去使用另外一个metafunction
template<typename T> struct ISReference : public false_type {};
template<typename T> struct ISReference<T&>: public true_type {};
template<typename T> struct ISReference<T&&>: public true_type {};
//特化来做选择分支
template<typename T> struct ISIntOrRefence: public ISReference<T> {};
template<> struct ISIntOrRefence<int>:public true_type {};
为了方便,会采用两种方式来简化
- 使用别名
//变量模板
template<typename T>
inline constexpr bool is_reference_v = ISIntOrRefence<T>::value;
//别名模板
template<typename T>
using remove_reference_t = typename remove_reference<T>::type;
std::cout<<is_reference_v<remove_reference_t<int&&>> << std::endl;
2.使用struct替代class,默认继承是public
template<typename T> struct ISReference : false_type {};
template<typename T> struct ISReference<T&>: true_type {};
template<typename T> struct ISReference<T&&>: true_type {};
例子1 type_identiy
template<typename T> struct type_identiy {using type = T;};
//一行写metafunction 可以把类的定义写到一行中
template<typename T> struct remove_reference :type_identiy<T>{};
template<typename T> struct remove_reference<T&> :type_identiy<T>{};
template<typename T> struct remove_reference<T&&> :type_identiy<T>{};
例子2 多形参is_same
//none-type metadata
template<bool b> struct bool_ {static constexpr bool value = b;};
using false_type = bool_<false>;
using true_type = bool_<true>;
template<typename T,typename U> struct is_same_v: false_type{};
template<typename T> struct is_same_v<T,T>:true_type {};
//编译期RTTI
int i;
is_same_v<decltype(i),int>::value
通过is_same实现分支逻辑
template<typename T>
int foo(T v) {
//if 是编译期间的语句
//所以要替换成 if constexpr (is_same_v<T,int>)
if (is_same_v<T,int>) {
return v; //不能从std::string 转换到 int
}
else if (is_same_v<T,std::string>) {
return std::stoi(v);
}
}
foo(std::string("1"));
多形参is_one_of
template<typename T,typename U,typename... Rest> struct is_one_of: bool_constant<is_one_of<T,U>::value || is_one_of<T,Rest...>::value>{};
template<typename T,typename U> struct is_one_of<T,U>:is_same_v<T,U>{};
//运行时 if判断是短路求值,判断一个成功后,就不会继续比较了
//但是编译期 短路求值不生效的,会继续展开
//使用别名
template<typename T>
using is_integral = is_one_of<T,bool,char,short,int,long long>;
模板模板形参
std::cout<<is_one_of<decltype(1.0f),int,float,double>::value<<std::endl;
std::list<int> li;
std::vector<int> vi;
std::cout<<is_instantialtion_of<decltype(vi),std::vector>::value<<std::endl;
//对于实例化 is_instantialtion_of<std::list<float>,std::list>
//模板实参 Inst = std::list<float> Tmpl = std::list
//实参带入特化 Tmpl<Args...> = std::list<float>,Tmpl = std::list
//推倒 针对第一个推倒,Tmpl = std::list,Args... = float
//对于第二个推倒 Tmpl = std::list ,ok
例子2 条件语句
//编译期if
template<typename T> struct type_identiy {using type = T;};
template<bool B,typename T,typename F>
struct conditianal: type_identiy<T>{};
template<typename T,typename F>
struct conditianal<false,T,F>: type_identiy<F>{};
template<typename T,typename F>
struct conditianal<true,T,F>: type_identiy<T>{};
template<typename T,typename U,typename... Rest> struct is_one_of : conditianal<is_same_v<T,U>::value,true_type,is_one_of<T,Rest...>> {};
template<typename T,typename U> struct is_one_of<T,U> : conditianal<is_same_v<T,U>::value,true_type,false_type>{};
std::cout<<conditianal<false,true_type,false_type>::type::value << std::endl;
例子3 处理数组