什么是元编程?

jrh*_*ath 15 c++ qt metaprogramming

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

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

JRH

jal*_*alf 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;

}
Run Code Online (Sandbox Code Playgroud)

一个非常简单的例子,但至少这些类型具有某种真实世界的相关性.此函数生成一个正确大小的数组,它在编译时完成,并具有完全类型安全性.它是通过硬编码值(我们可能想要连接大量不同大小的数组)或者在运行时(因为那时我们丢失了类型信息)来计算我们无法轻易完成的事情.

但更常见的是,您倾向于使用元编程来表示类型,而不是值.

可以在标准库中找到一个很好的例子.每个容器类型定义自己的迭代器类型,但普通的旧指针可以用作迭代器.从技术上讲,迭代器需要公开一些typedef成员,例如value_type,指针显然不会这样做.所以我们使用一些元编程来说"哦,但如果迭代器类型变成指针,它value_type应该使用这个定义."

关于这一点有两点需要注意.第一个是我们操纵类型,而不是值我们不是说"N的因子是如此",而是" value_type类型T的定义为......"

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

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


Rob*_*uld 8

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

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


Shu*_*oUk 7

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

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

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

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

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

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

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

尺寸类型(测量单位)

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

高阶元功能

两次(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> {};
};
Run Code Online (Sandbox Code Playgroud)

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

BOOST_STATIC_ASSERT((
    boost::is_same<
         twice<add_pointer_f, int>::type
       , int**
    >::value
));
Run Code Online (Sandbox Code Playgroud)


Pau*_*ams 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; 
Run Code Online (Sandbox Code Playgroud)

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

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

  • 这里的优点是在编译时计算因子5.这样做的问题是,如果你执行cout << fact <MAX_INT>,它也会在编译时计算,并会导致更长的构建时间.这仍然是一个巧妙的伎俩 (3认同)
  • 但格伦的例子仍然有效; 模板元编程所做的工作越多,构建时间就越长. (2认同)
  • :)恰好是一个没有其他用途的例子,而不是显示它是可能的. (2认同)