分支代码元编程示例

分支代码的元编程

最简单的编译期执行的分支语句

直接使用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,替换失败并非错误)引入分支

什么是SFINAE

基于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