15

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

同样,Qt 的元对象系统是元编程的一种形式吗?

jrh

4

10 回答 10

26

到目前为止,大多数示例都对值进行了操作(计算 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 的阶乘是某某”,而是“value_type类型 T 的定义为……”

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

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

于 2009-06-11T12:00:30.137 回答
9

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

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

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

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

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

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

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

标注类型(度量单位)

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

高阶元函数

两次(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
));
于 2009-06-11T11:17:44.857 回答
8

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

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

于 2009-06-11T11:32:10.797 回答
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; 

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

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

于 2009-06-11T11:08:48.497 回答
4

另一个例子:在这种情况下,元编程技术用于在编译时使用 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;
}
于 2009-06-11T11:31:29.520 回答
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 次方的递归计算发生在编译时而不是运行时。因此,它在运行时性能方面非常有效,但代价是编译速度较慢。

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

于 2009-06-11T11:11:16.247 回答
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;
}
于 2009-06-11T12:05:31.840 回答
2

我需要在日常工作中使用 Boost.MPL 的唯一一次是当我需要boost::variantQVariant.

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

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

于 2009-07-28T09:23:01.307 回答
0

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

于 2009-06-18T01:31:41.650 回答
0

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

宏 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
于 2011-12-07T17:39:04.790 回答