代码之家  ›  专栏  ›  技术社区  ›  jrharshath

什么是元编程?

  •  15
  • jrharshath  · 技术社区  · 17 年前

    关于 this question ,有人能解释和发布元编程的示例代码吗?我在谷歌上搜索了这个词,但没有找到任何例子来说服我它可以有任何实际用途。

    同样的,是 Qt's Meta Object System 元编程的一种形式?

    JRH

    10 回复  |  直到 17 年前
        1
  •  25
  •   Stack Overflow is garbage    17 年前

    到目前为止,大多数例子都是在值上操作的(计算π的位数,n的阶乘或类似的数值),这些例子基本上都是教科书中的例子,但它们通常不是很有用。很难想象在这种情况下,你真的需要编译器来计算π的第17位。要么自己硬编码,要么在运行时计算。

    与现实世界更相关的一个例子可能是:

    假设我们有一个数组类,其中的大小是一个模板参数(因此这将声明一个10个整数的数组: array<int, 10> )

    现在我们可能想要连接两个数组,并且我们可以使用一些元编程来计算得到的数组大小。

    template <typename T, int lhs_size, int rhs_size>
    array<T, lhs_size + rhs_size> concat(const array<T, lhs_size>& lhs, const array<T, rhs_size>& rhs){
    
      array<T, lhs_size + rhs_size> result;
      // copy values from lhs and rhs to result
      return result;
    
    }
    

    一个非常简单的例子,但至少类型具有某种现实世界的相关性。这个函数生成一个大小正确的数组,它在编译时这样做,并且具有完整的类型安全性。它正在计算一些我们不容易做到的事情,要么是通过对值进行硬编码(我们可能想要连接许多不同大小的数组),要么是在运行时(因为这样我们会丢失类型信息)。

    不过,更常见的是,您倾向于对类型使用元编程,而不是对值使用元编程。

    在标准库中可以找到一个很好的例子。每个容器类型定义自己的迭代器类型,但是普通的旧指针可以 用作迭代器。 从技术上讲,迭代器需要公开许多typedef成员,例如 value_type 很明显,指针不会这样做。所以我们使用一些元编程来表示“哦,但是如果迭代器类型是指针,那么 值类型 应改为使用此定义。“

    有两件事要注意。首先,我们是在操纵类型,而不是值,我们不是说“n的阶乘是So-So”,而是说,“the 值类型 类型t的定义为…”

    第二件事是它被用来促进通用编程。(如果迭代器不适用于所有最简单的例子(指向数组的指针),它就不是一个非常通用的概念。因此,我们使用一些元编程来填充将指针视为有效迭代器所需的详细信息。

    这是元编程的一个相当常见的用例。当然,您可以将其用于广泛的其他用途(表达式模板是另一个常用的示例,旨在优化昂贵的计算并提高计算效率。spirit是一个完全落伍的示例,允许您在编译时定义自己的分析器),但最常见的用法可能是平滑这些小的障碍。以及一些需要特殊处理并使一般编程成为不可能的角落案例。

        2
  •  8
  •   Robert Gould    17 年前

    虽然它很大(2000 LoC),但我在C++中做了一个自反类系统,它是编译器独立的,包括对象编组和元数据,但是没有存储开销或访问时间惩罚。它是一个核心元编程,并被用于一个非常大的在线游戏,用于映射网络传输和数据库映射(ORM)的游戏对象。

    不管怎样,编译需要一段时间,大约5分钟,但是它的好处是,对于每个对象来说,它的速度和手工调优的代码一样快。因此,它通过减少服务器上的大量CPU时间(CPU使用率是以前的5%)来节省大量资金。

        3
  •  7
  •   ShuggyCoUk    17 年前

    这个概念完全来源于这个名字 Meta- 意思是从它的前缀中提取。
    以更“对话式”的方式处理事情,而不是处理事情本身。

    在这方面,元编程本质上是编写代码,这会编写(或导致编写)更多的代码。

    C++模板系统是元编程,因为它不简单地做文本替换(如C预处理器那样),而是有一种(复杂的和低效的)与它解析的代码结构交互的输出代码,这些代码要复杂得多。在这方面,C++中的模板预处理是图灵完成。这不是一个 要求 可以说有些东西是元编程,但几乎可以肯定 足够的 这样算。

    如果模板逻辑足够复杂,则可将可参数化的代码生成工具视为元编程。

    系统越接近于使用表示语言的抽象语法树(而不是我们表示它的文本形式),就越有可能被视为元编程。

    从Qt元对象代码来看,我不会(从草率的检查)调用它通常用于C++模板系统或LISP宏的东西的元编程。它似乎只是一种代码生成的形式,在编译阶段将一些功能注入到现有的类中(它可以被看作是当前流行的面向方面编程风格的先驱,或者是JavaScript等语言中基于原型的对象系统

    作为一种极端长度的例子,你可以在C++中使用 Boost MPL 谁的 tutorial 演示如何获取:

    Dimensioned types (计量单位)

    quantity<float,length> l( 1.0f );
    quantity<float,mass> m( 2.0f );
    m = l;    // compile-time type error
    

    Higher Order Metafunctions

    两次(f,x):=f(f(x))

    template <class F, class X>
    struct twice
      : apply1<F, typename apply1<F,X>::type>
    {};
    
    struct add_pointer_f
    {
        template <class T>
        struct apply : boost::add_pointer<T> {};
    };
    

    现在,我们可以使用两次with add_pointer_f来构建指向指针的指针:

    BOOST_STATIC_ASSERT((
        boost::is_same<
             twice<add_pointer_f, int>::type
           , int**
        >::value
    ));
    
        4
  •  5
  •   Thomas L Holaday    17 年前

    下面是一个常见的例子:

      template <int N>
      struct fact {
          enum { value = N * fact<N-1>::value };
      };
    
      template <>
      struct fact<1> {
          enum { value = 1 };
      }; 
    
      std::cout << "5! = " << fact<5>::value << std::endl; 
    

    你基本上是用模板来计算阶乘的。

    我最近看到的一个更实际的例子是一个基于DB表的对象模型,它使用模板类来为基础表中的外键关系建模。

        5
  •  4
  •   Gian Paolo Ghilardi    17 年前

    另一个例子:在这种情况下,使用元编程技术,在编译时使用高斯-勒让德算法获得任意精度的pi值。

    我为什么要在现实世界中使用类似的东西?例如,为了避免重复计算,为了获得更小的可执行文件,为了在特定的体系结构上最大限度地提高性能而调整代码,…

    就我个人而言,我喜欢元编程,因为我讨厌重复这些东西,而且我可以利用体系结构限制来调整常量。

    希望你喜欢。

    只有我的2美分。

    /**
     *  FILE     : MetaPI.cpp
     *  COMPILE  : g++ -Wall -Winline -pedantic -O1 MetaPI.cpp -o MetaPI
     *  CHECK    : g++ -Wall -Winline -pedantic -O1 -S -c MetaPI.cpp [read file MetaPI.s]
     *  PURPOSE  : simple example template metaprogramming to compute the
     *             value of PI using [1,2].
     *
     *  TESTED ON:
     *  - Windows XP, x86 32-bit, G++ 4.3.3
     *
     *  REFERENCES:
     *  [1]: http://en.wikipedia.org/wiki/Gauss%E2%80%93Legendre_algorithm
     *  [2]: http://www.geocities.com/hjsmithh/Pi/Gauss_L.html
     *  [3]: http://ubiety.uwaterloo.ca/~tveldhui/papers/Template-Metaprograms/meta-art.html
     *
     *  NOTE: to make assembly code more human-readable, we'll avoid using
     *        C++ standard includes/libraries. Instead we'll use C's ones.
     */
    
    #include <cmath>
    #include <cstdio>
    
    template <int maxIterations>
    inline static double compute(double &a, double &b, double &t, double &p)
    {
        double y = a;
        a = (a + b) / 2;
        b = sqrt(b * y);
        t = t - p * ((y - a) * (y - a));
        p = 2 * p;
    
        return compute<maxIterations - 1>(a, b, t, p);
    }
    
    // template specialization: used to stop the template instantiation
    // recursion and to return the final value (pi) computed by Gauss-Legendre algorithm
    template <>
    inline double compute<0>(double &a, double &b, double &t, double &p)
    {
        return ((a + b) * (a + b)) / (4 * t);
    }
    
    template <int maxIterations>
    inline static double compute()
    {
        double a = 1;
        double b = (double)1 / sqrt(2.0);
        double t = (double)1 / 4;
        double p = 1;
    
        return compute<maxIterations>(a, b, t, p); // call the overloaded function
    }
    
    int main(int argc, char **argv)
    {
        printf("\nTEMPLATE METAPROGRAMMING EXAMPLE:\n");
        printf("Compile-time PI computation based on\n");
        printf("Gauss-Legendre algorithm (C++)\n\n");
    
        printf("Pi=%.16f\n\n", compute<5>());
    
        return 0;
    }
    
        6
  •  2
  •   anon    17 年前

    下面的例子摘自这本好书 C++ Templates - The complete guide .

    #include <iostream>
    using namespace std;
    
    template <int N> struct Pow3 {
       enum { pow = 3 * Pow3<N-1>::pow };
    }
    
    template <> struct Pow3<0> {
       enum { pow = 1 };
    }
    
    int main() {
       cout << "3 to the 7 is " << Pow<7>::pow << "\n";
    }
    

    这段代码的要点是,对3的第7次幂的递归计算发生在编译时,而不是运行时。因此,在运行时性能方面,它是非常高效的,代价是编译速度较慢。

    这有用吗?在这个例子中,可能不是。但也存在一些问题,即在编译时执行计算可能是一种优势。

        7
  •  2
  •   xtofl Adam Rosenfield    17 年前

    很难说什么是C++元编程 . 越来越多的我觉得它很像引入“类型”作为变量,就像函数式编程那样。它使C++中的声明式编程成为可能。

    举例比较容易。

    我最喜欢的是一个“技巧”(或模式:)来平展乘法嵌套 switch/case 阻碍:

    #include <iostream>
    using namespace std;
    
    enum CCountry { Belgium, Japan };
    enum CEra     { ancient, medieval, future };
    
    // nested switch
    void historic( CCountry country, CEra era ) {
      switch( country ) {
            case( Belgium ):
              switch( era ) {
                case( ancient ): cout << "Ambiorix"; break;
                case( medieval ): cout << "Keizer Karel"; break;
              }
              break;
            case( Japan ):
              switch( era ) {
                case( future ): cout << "another Ruby?"; break;
                case( medieval ): cout << "Musashi Mijamoto"; break;
              }
              break;
      }
    }
    
    
    // the flattened, metaprogramming way
    // define the conversion from 'runtime arguments' to compile-time arguments (if needed...)
    // or use just as is.
    template< CCountry country, CEra era > void thistoric();
    
    
    template<> void thistoric<Belgium, ancient> () { cout << "Ambiorix"; }
    template<> void thistoric<Belgium, medieval>() { cout << "Keizer Karel"; }
    template<> void thistoric<Belgium, future  >() { cout << "Beer, lots of it"; }
    
    template<> void thistoric<Japan, ancient> () { cout << "wikipedia"; }
    template<> void thistoric<Japan, medieval>() { cout << "Musashi"; }
    template<> void thistoric<Japan, future  >() { cout << "another Ruby?"; }
    
    
    // optional: conversion from runtime to compile-time
    //
    template< CCountry country > struct SelectCountry {
      static void select( CEra era ) {
        switch (era) {
              case( medieval ): thistoric<country, medieval>(); break;
              case( ancient  ): thistoric<country, ancient >(); break;
              case( future   ): thistoric<country, future  >(); break;
    
        }
      }
    };
    
    void Thistoric ( CCountry country, CEra era ) {
        switch( country ) {
              case( Belgium ): SelectCountry<Belgium>::select( era ); break;
              case( Japan   ): SelectCountry<Japan  >::select( era ); break;
        }
      } 
    
    
    
    int main() {   
      historic( Belgium, medieval ); // plain, nested switch
      thistoric<Belgium,medieval>(); // direct compile time switch
      Thistoric( Belgium, medieval );// flattened nested switch
      return 0;
    }
    
        8
  •  2
  •   Marc Mutz - mmutz    16 年前

    在我的日常工作中,我唯一需要使用boost.mpl的时候就是我需要转换 boost::variant 往返 QVariant .

    自从 Boo::变体 具有O(1)访问机制, Boo::变体 q-变体 方向是微不足道的。

    然而, q-变体 没有访问机制,因此要将其转换为 Boo::变体 ,您需要迭代 mpl::list 特定的 Boo::变体 实例化可以保存,对于每种类型,请 q-变体 是否包含该类型,如果包含,则提取该值并在 Boo::变体 . 很有趣,你应该试试看:)

        9
  •  0
  •       17 年前

    qtmetaObject基本实现反射( Reflection ) 元编程的主要形式之一,实际上相当强大。它类似于Java的反射,它也常用于动态语言(Python、Ruby、PHP……)。它比模板更易读,但两者都有各自的优缺点。

        10
  •  0
  •   Adisak    14 年前

    这是一个简单的“值计算”沿着阶乘的路线。然而,它是您在代码中实际使用的可能性更大的一个。

    宏ct_NextPowerOftwo2(val)使用模板元编程计算两个幂的下一个幂,该幂大于或等于编译时已知值的值。

    template<long long int POW2VAL> class NextPow2Helper
    {
        enum { c_ValueMinusOneBit     = (POW2VAL&(POW2VAL-1)) };
    public:
        enum {
            c_TopBit                      = (c_ValueMinusOneBit) ?
                NextPow2Helper<c_ValueMinusOneBit>::c_TopBit : POW2VAL,
            c_Pow2ThatIsGreaterOrEqual    = (c_ValueMinusOneBit) ?
                (c_TopBit<<1) : c_TopBit
        };
    };
    template<> class NextPow2Helper<1>
    { public: enum { c_TopBit = 1, c_Pow2ThatIsGreaterOrEqual = 1 }; };
    template<> class NextPow2Helper<0>
    { public: enum { c_TopBit = 0, c_Pow2ThatIsGreaterOrEqual = 0 }; };
    // This only works for values known at Compile Time (CT)
    #define CT_NEXTPOWEROFTWO2(VAL) NextPow2Helper<VAL>::c_Pow2ThatIsGreaterOrEqual