第十五章:元编程(一)

元编程的引入

在引入元编程之前我们需要回顾下泛型编程,泛型编程是使用一套代码来处理不同的类型。但对于一些特殊的类型需要引入额外的处理逻辑,也就是在编译期引入操作程序的程序-元编程。或者更为通俗的讲,可以将元编程理解为编译期计算。

我们可以使用编译期计算来辅助运行期计算,但需要在概念上着重强调两点

  • 这种辅助并不是简单地将整个运算一分为二
  • 我们需要详细分析哪些内容可以放到编译期,哪些需要放到运行期。如果某种信息需要在运行期确定,那么通常无法利用编译期计算

元程序的形式通常有以下几种:模板, 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 引入分支

    1. 基于 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;
      }
      

      注意用做缺省模板实参不能引入分支!

    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;
    
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值