第十五章:元编程(一)

元编程的引入

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

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

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

元程序的形式通常有以下几种:模板, 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 
    #include 
    #include 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;
    


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部