什么是元编程?

时间:2009-06-11 11:01:54

标签: c++ qt metaprogramming

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

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

JRH

10 个答案:

答案 0 :(得分:25)

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

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

假设我们有一个数组类,其中size是一个模板参数(所以这将声明一个包含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定义为......”< / p>

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

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

答案 1 :(得分:8)

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

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

答案 2 :(得分:7)

这个概念完全来自名称Meta-,意思是从它前缀的东西中抽象出来 用更多的“会话风格”来做事物而不是事物本身。

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

C ++模板系统是元编程,因为它不仅仅是文本替换(如c预处理器那样),而是具有(复杂且低效)的方式与它解析的代码结构交互以输出更多的代码复杂。在这方面,C ++中的模板预处理是Turing完成的。这不是要求,说某些东西是元编程,但几乎可以肯定足够被计算在内。

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

系统越接近使用代表语言的抽象语法树(与我们在其中表示的文本形式相反),就越有可能将其视为元编程。

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

作为极端长度的例子,您可以在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> {};
};
  

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

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

答案 3 :(得分:5)

这是一个常见的例子:

  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; 

您基本上使用模板来计算阶乘。

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

答案 4 :(得分:4)

另一个例子:在这种情况下,元编程tecnique用于在编译时使用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;
}

答案 5 :(得分:2)

以下示例取自优秀的书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次幂的递归计算是在编译时而不是运行时进行的。因此,在运行时性能方面非常高效,但代价是编译速度较慢。

这有用吗?在这个例子中,可能不是。但是在编译时执行计算可能是一个优点。

答案 6 :(得分:2)

很难说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;
}

答案 7 :(得分:2)

我需要在我的日常工作中使用Boost.MPL的唯一一次是我需要将boost::variant转换为QVariant和从boost::variant转换。

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

但是,boost::variant没有访问机制,因此为了将其转换为mpl::list,您需要迭代boost::variant特定类型的QVariant boost::variant 1}}实例化可以保持,并且对于每种类型,询问{{1}}它是否包含该类型,如果是,则提取值并将其返回{{1}}。这很有趣,你应该尝试一下:)

答案 8 :(得分:0)

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

答案 9 :(得分:0)

这是一个简单的“价值计算”,与Factorial一致。但是,它是您更有可能在代码中实际使用的那个。

宏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