分支代码的元编程
最简单的编译期执行的分支语句
直接使用constexpr:
#include<iostream>
#include<type_traits>
constexpr int fun(int x)
{
if(x > 3)
return x * 2;
else
return x - 100;
}
constexpr int x = fun(100);
但是,这种方法的应用非常有限,生成的也是编译期常量。
基于if constexpr 的分支
便于理解且只能处理数值,同时要小心引入运行期计算。例如下面运用数值模板的例子。
template<int x>
int func()
{
if constexpr (x > 3)
return x * 2;
else
retrun x - 100;
}
int y = func<100>();
值得注意的是,这里的func()函数不是在编译期而是在运行期执行的。但由于这是数值模板,所以参数必须在编译期获得。同时分支语句也会在编译期处理。
之所以要小心在运行期运算,是因为有时候会忘记写if constexpr,这样就会将编译期的分支退化为运行期的计算。
基于(偏)特化引入分支
这是一种常见的元编程方式但书写较为麻烦。比如:
#include<type_traits>
template <int x>
struct Imp
{
constexpr static int value = x * 2;
};
template<>
struct Imp<100>
{
constexpr static int value = 100 - 3;
};
constexpr int x = Imp<100>::value;
这种偏特化的方式不仅可以处理数值,还可以处理类型和模板。比如下面的例子就是利用偏特化返回不同的类型:
#include<type_traits>
template <int x>
struct Imp
{
constexpr static int value = x * 2;
using type = int;
};
template<>
struct Imp<100>
{
constexpr static int value = 100 - 3;
using type = double;
};
using type_ = Imp<100>::type;
而在C++20中,我们还可以利用concept引入对模板的类型限制来实现分支语句。
template<int x>
struct Imp;
template<int x>
requires (x < 100)
struct Imp<x>
{
constexpr static int value = x * 2;
using type = int;
};
template<int x>
requires (x >= 100)
struct Imp<x>
{
constexpr static int value = 100 - 3;
using type = double;
};
constexpr int x = Imp<97>::value;
std::cout << x << "\n";
输出为:
194
利用std::conditional引入分支
template< bool B, class T, class F >
struct conditional;
提供成员 typedef type ,若 B 在编译时为 true 则定义为 T ,或若 B 为 false 则定义为 F 。添加 conditional 的特化的程序行为未定义。
#include<iostream>
#include<type_traits>
#include<typeinfo>
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
std::conditional语法简单但应用场景受限(只能返回类型,类似于运行期的三元表达式)。
经典的利用SFINAE(Substitution failure is not an error,替换失败并非错误)引入分支
基于std::enable_if引入分支
语法不易懂但功能强大
可能的实现:
template<bool B, class T = void>
struct enable_if {};
template<class T>
struct enable_if<true, T> { typedef T type; };
如果B为true的时候,才可以得到一个type,例如 template< bool B, class T = void > using enable_if_t = typename enable_if<B,T>::type;
而如果为false,以上代码自然就无效了。于是就出现了所谓的匹配失败(SFINAE)
而std::enable_if_t基于模板的 SFINAE 和 匿名类型参数 的基础概念上进行了简洁且完美的封装。
enable_if_t 强制使用 enable_if 的 ::type 来触发 SFINAE 规则, 如果失败则跳过当前匹配进入下一个匹配。
template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;
先看一种在函数模板中的应用:
#include<iostream>
#include<type_traits>
#include<typeinfo>
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 - 3;
}
constexpr auto x = fun<97>();
std::cout << x << "\n";
输出为:
194
还可以在类模板中使用:
template<int x, typename = void*>
struct Imp;
template<int x>
struct Imp<x, std::enable_if_t<(x < 100)>*>
{
constexpr static int value = x * 2;
using type = int;
};
template<int x>
struct Imp<x, std::enable_if_t<(x >= 100)>*>
{
constexpr static int value = x - 3;
using type = double;
};
constexpr auto x = Imp<101>::value;
std::cout << x << "\n";
输出为:
98
注意用缺省模板实参不能引入分支! 否则会被看做重定义。
C++17:基于std::void_t的分支
template< class... >
using void_t = void;
这是一个可变长度的别名模板。
基于三元运算符的分支
template<int x>
constexpr auto fun = (x < 100) ? x * 2 : x - 3;
constexpr auto x = fun<102>;
std::cout << x << "\n";
输出为:
99