【问题标题】:What is metaprogramming?什么是元编程?
【发布时间】:2010-11-02 02:28:37
【问题描述】:

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

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

jrh

【问题讨论】:

  • 我不是 QT 程序员,但它看起来确实像元编程的一种形式。但是,当 C++ 程序员谈论元编程时,他们通常会专门指模板元编程(这与 QT 的元对象系统无关)。存在其他几种风格:一些语言允许您在运行时进行元编程(在程序运行时操作或扩展类型),而 QT 的方法似乎实际上是生成要包含的新代码文件。但这都是元编程,因为元程序只是一个生成或操作程序的程序。
  • 您对一般元编程感兴趣还是只对 C++ 模板元编程感兴趣?

标签: c++ qt metaprogramming


【解决方案1】:

到目前为止,大多数示例都对值进行了操作(计算 pi 的数字、N 的阶乘或类似的),这些几乎都是教科书示例,但它们通常不是很有用。很难想象你真的需要编译器来计算 pi 的第 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,而指针显然不这样做。所以我们用一点元编程来表达“哦,但是如果迭代器类型是指针,那么它的value_type 应该使用这个定义。”

对此有两点需要注意。首先是我们在操作类型,而不是值我们不是说“N 的阶乘是某某”,而是“T 类型的 value_type 被定义为……”

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

这是元编程的一个相当常见的用例。当然,您可以将它用于广泛的其他目的(表达式模板是另一个常用示例,旨在优化昂贵的计算,而 Boost.Spirit 是一个完全过火并允许您在编译时定义自己的解析器的示例-时间),但最常见的用途可能是平滑这些小颠簸和极端情况,否则这些情况需要特殊处理并使通用编程变得不可能。

【讨论】:

  • ...最后是一个真实世界的例子 - 根据要求:)
  • 我确实使用模板元编程 (TMP) 来进行我经常使用的一种类型的值计算。我有一个简单的 TMP,它计算下一个大于或等于常数的 2 次幂。我在运行时根据结构大小使用它进行 2 次幂 bin 选择。
  • 对元编程尖叫的一件事是在编译时工作的任何类型的数据库 ORM,最简单的情况是能够生成将适当的 CREATE TABLE 指令定义为 std::string 类静态方法的元分类,自动列索引枚举(用于 sqlite3_column_* 系列)等
【解决方案2】:

这个概念完全来自Meta-这个名字,意思是从它前缀的东西中抽象出来。
以更“对话式”的方式处理事物而不是事物本身。

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

C++ 模板系统是元编程,因为它不只是进行文本替换(如 c 预处理器所做的那样),而是具有(复杂且低效)与它解析的代码结构进行交互以输出代码的方式远不止这些复杂的。在这方面,C++ 中的模板预处理是图灵完备的。这不是说某事是元编程的要求,但几乎可以肯定足够算作元编程。

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

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

通过查看 QT MetaObjects 代码,我不会(粗略地检查一下)将其称为元编程,因为它通常保留用于 C++ 模板系统或 Lisp 宏之类的东西。它似乎只是一种代码生成形式,它在编译阶段将一些功能注入到现有类中(它可以被视为当前流行的面向方面编程风格或 JavaScript 等语言中基于原型的对象系统的先驱

作为您可以在 C++ 中使用的极端长度的示例,Boost MPLtutorial 向您展示了如何获取:

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> {};
};

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

BOOST_STATIC_ASSERT((
    boost::is_same<
         twice<add_pointer_f, int>::type
       , int**
    >::value
));

【讨论】:

    【解决方案3】:

    虽然它很大(2000loc),但我在 c++ 中创建了一个自反类系统,它独立于编译器,包括对象编组和元数据,但没有存储开销或访问时间损失。它是核心元编程,被用于大型在线游戏中,用于映射游戏对象以进行网络传输和数据库映射 (ORM)。

    无论如何编译需要一段时间,大约 5 分钟,但它的好处是与每个对象的手动调整代码一样快。因此,它通过显着减少服务器上的 CPU 时间来节省大量资金(CPU 使用率是过去的 5%)。

    【讨论】:

    • 将在不久的将来在系统上发表一篇文章,在我与我们的律师协商后。
    • 听起来很有趣! :D 我期待这篇文章。
    • 这个有什么更新吗?我会对查看代码非常感兴趣,因为非开销元数据总是很有趣。
    【解决方案4】:

    这是一个常见的例子:

      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 是在编译时计算的。这样做的问题是,如果你这样做 cout 它也在编译时计算,并且会导致更长的构建时间。仍然是一个巧妙的技巧
    • 事实 不可能计算编译时间。 AFAIK,在这种递归情况下,编译器使用模板类实例化的可配置限制......
    • 但格伦示例的要点仍然有效;使用模板元编程完成的工作越多,构建时间就越长。
    • :) 正是一个例子,除了表明它是可能的之外别无用处。
    【解决方案5】:

    另一个例子:在这种情况下,元编程技术用于在编译时使用 Gauss-Legendre 算法获取任意精度的 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;
    }
    

    【讨论】:

    • 函数 sqrt() 不会在编译时运行,所以这不是在编译时计算 PI 的值,对吧?它只是在编译时布置了一个递归函数调用深度。
    【解决方案6】:

    以下示例摘自优秀书籍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】:

      很难说什么是 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】:

        我在日常工作中唯一需要使用 Boost.MPL 的时候是需要在 boost::variantQVariant 之间进行转换。

        由于boost::variant 具有O(1) 访问机制,因此boost::variantQVariant 的方向几乎是微不足道的。

        但是,QVariant 没有访问机制,因此为了将其转换为 boost::variant,您需要遍历特定 boost::variant 实例化可以持有的类型的 mpl::list,并且对于每种类型,询问QVariant 是否包含该类型,如果是,则提取值并将其返回到boost::variant。挺好玩的,一定要试试:)

        【讨论】:

          【解决方案9】:

          QtMetaObject 基本上实现了反射(Reflection),元编程的主要形式之一,实际上相当强大。它类似于 Java 的反射,也常用于动态语言(Python、Ruby、PHP...)。它比模板更具可读性,但两者各有利弊。

          【讨论】:

            【解决方案10】:

            这是一个沿阶乘线的简单“值计算”。但是,您更有可能在代码中实际使用它。

            宏 CT_NEXTPOWEROFTWO2(VAL) 使用模板元编程来计算大于或等于编译时已知值的下一个 2 的幂。

            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
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-23
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2018-08-05
              相关资源
              最近更新 更多