参考this question,有人可以解释和发布元编程的示例代码吗?我用谷歌搜索了这个词,但我没有找到任何例子来说服它可以用于任何实际用途。
同样,Qt's Meta Object System是元编程的一种形式吗?
JRH
答案 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
两次(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)访问机制,QVariant
到QVariant
方向几乎是微不足道的。
但是,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