元编程的引入
在引入元编程之前我们需要回顾下泛型编程,泛型编程是使用一套代码来处理不同的类型。但对于一些特殊的类型需要引入额外的处理逻辑,也就是在编译期引入操作程序的程序-元编程。或者更为通俗的讲,可以将元编程理解为编译期计算。
我们可以使用编译期计算来辅助运行期计算,但需要在概念上着重强调两点
- 这种辅助并不是简单地将整个运算一分为二
- 我们需要详细分析哪些内容可以放到编译期,哪些需要放到运行期。如果某种信息需要在运行期确定,那么通常无法利用编译期计算
元程序的形式通常有以下几种:模板, constexpr 函数,其它编译期可使用的函数(如 sizeof ),在C++中元程序通常以函数为单位,也被称为函数式编程。
接下来我们需要关注元编程中可以处理的数据,也称为元数据,主要有以下几种:
- 基本元数据:数值、类型、模板
- 数组
元程序具有以下两个主要的性质:
- 输入输出均为“常量”
- 函数无副作用:对于相同的输入会产生相同的输出
在C++11中引入一个type_traits
元编程库,它主要用作元编程的基本组件,具体的可以参考这里
顺序代码的编写方式
我们首先来看一个简单的例子:为输入类型去掉引用并添加const
template <typename T>
struct Fun
{
using RemRef = typename std::remove_reference<T>::type;
using type = typename std::add_const<RemRef>::type;
};
int main() {
Fun<int &>::type x = 3;
return 0;
}
代码无需至于函数中:通常会置于模板中,以头文件的形式提供。
我们也可以编写更加复杂的顺序代码:
- 以数值、类型、模板作为输入
- 以数值、类型、模板作为输出
在使用类作为载体之后,我们可以引入权限限定符来防止误用,比如
template <typename T>
struct Fun
{
private:
using RemRef = typename std::remove_reference<T>::type;
public:
using type = typename std::add_const<RemRef>::type;
};
最后,我们还可以通过引入别名模版来简化调用方式,比如
template <typename T>
struct Fun_
{
private:
using RemRef = typename std::remove_reference<T>::type;
public:
using type = typename std::add_const<RemRef>::type;
};
template <typename T>
constexpr auto Fun = Fun_<T>::type ;
分支代码的编写方式
接下来我们将讨论六种分支代码的编写方式:
-
基于
if constexpr
的分支:便于理解只能处理数值,同时要小心引入运行期计算(遗漏掉constexpr
)template <int x> int fun(){ if constexpr (x > 3){ return x * 2; } else{ return x - 100; } }
-
基于(偏)特化引入分支:常见分支引入方式但书写麻烦
template <int x> struct Imp{ constexpr static int value = x * 2; }; template<> struct Imp<100>{ constexpr static int value = 100 - 3; };
-
基于
std::conditional
引入分支:语法简单但应用场景受限#include <iostream> #include <type_traits> #include <typeinfo> int main() { typedef std::conditional<true, int, double>::type Type1; typedef std::conditional<false, int, double>::type Type2; typedef std::conditional<sizeof(int) >= sizeof(double), int, double>::type Type3; std::cout << typeid(Type1).name() << '\n'; std::cout << typeid(Type2).name() << '\n'; std::cout << typeid(Type3).name() << '\n'; } 输出分别为: int double double
-
基于 SFINAE 引入分支
-
基于 std::enable_if 引入分支:语法不易懂但功能强大
template <int x, std::enable_if_t<(x>=100)>* = nullptr> constexpr auto fun(){ return x * 2; } template <int x, std::enable_if_t<(x<100)>* = nullptr> constexpr auto fun(){ return x - 2; }
注意用做缺省模板实参不能引入分支!
-
基于
std::void_t
引入分支: C++17 中的新方法,通过“无效语句”触发分支,具体的例子参考这里
-
-
基于
concept
引入分支: C++20 中的方法,可用于替换enable_if
,具体的例子参见上一章 -
基于三元运算符引入分支: std::conditional 的数值版本
template <int x> constexpr auto fun = (x<100) ? x * 2 : x - 3;