TMP(1)

简介

元编程是更高层次的抽象,对代码进行编程。把程序甚至自己作为输入数据来处理,比如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的原因

  1. 可以编译期计算,提升运行时效率,通常会导致编译期时间变长,以及编译出的文件大小变大

  2. 可以针对类型做计算,由于c++缺少自省机制

    if(typeof(var) == int){}else{} //psedo code
    
    
  3. tmp可以让代码变得简单优美

    py::class_<Student>("m","Student")
     .def (py:init<const std::string&>())
     .def ("set_name",&Student::set_name)
    
  4. 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 {};

为了方便,会采用两种方式来简化

  1. 使用别名
//变量模板
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 处理数组


©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容