Что такое метапрограммирование?
Что касается этого вопроса, может кто-нибудь объяснить и отправить пример кода метапрограммирования? Я придумал этот термин, но не нашел примеров, чтобы убедить меня, что это может быть практическим.
В то же время, Qt Meta Object System является формой метапрограммирования?
JRH
Ответы
Ответ 1
Большинство примеров до сих пор работали над значениями (вычисления цифр pi, факториала N или аналогичных), и это в значительной степени примеры учебников, но они обычно не очень полезны. Трудно представить себе ситуацию, когда вам действительно нужен компилятор для вычисления 17-й цифры pi. Либо вы его жестко программируете, либо вычисляете его во время выполнения.
Примером, который может быть более актуальным для реального мира, может быть следующее:
Скажем, у нас есть класс массива, где размер является параметром шаблона (так что это объявит массив из 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 - пример полного за борта и позволяющий вам определять свой собственный парсер при компиляции, время), но, вероятно, наиболее распространенным является сглаживание этих маленьких ударов и угловых случаев, которые в противном случае потребовали бы специальной обработки и не сделали бы общее программирование невозможным.
Ответ 2
Хотя он большой (2000loc), я создал рефлексивную систему классов внутри С++, которая является независимой от компилятора и включает в себя объекты маршаллинга и метаданные, но не имеет накладных расходов на хранение или время доступа. Это хардкорное метапрограммирование и используется в очень большой онлайн-игре для картографирования игровых объектов для сетевой передачи и сопоставления баз данных (ORM).
В любом случае требуется некоторое время, чтобы скомпилировать около 5 минут, но имеет преимущество быть таким же быстрым, как ручной настраиваемый код для каждого объекта. Таким образом, это экономит много денег за счет сокращения значительного времени процессора на наших серверах (использование процессора составляет 5% от того, что раньше было).
Ответ 3
Концепция целиком исходит от имени Meta- > означает абстрагироваться от того, на что он префикс.
В более "разговорном стиле" делать что-то с вещью, а не с самой вещью.
В этом отношении метапрограммирование - это, по сути, код, который записывает (или вызывает запись) больше кода.
Система шаблонов С++ представляет собой метапрограммирование, поскольку она не просто выполняет текстовую подстановку (как это делает препроцессор c), но имеет (сложное и неэффективное) средство взаимодействия со структурой кода, которое она анализирует для вывода кода, который намного больше сложный. В этом отношении предварительная обработка шаблона в С++ завершается Тьюрингом. Это не требование сказать, что что-то метапрограммируется, но почти наверняка достаточно, чтобы считаться таковым.
Инструменты генерации кода, которые являются параметризуемыми, могут считаться метапрограммирующими, если их шаблонная логика достаточно сложна.
Чем ближе система переходит к работе с абстрактным синтаксическим деревом, которое представляет язык (в отличие от текстовой формы, в которой мы его представляем), тем более вероятно, что это будет считаться метапрограммированием.
От взгляда на код QT MetaObjects я бы (из беглого осмотра) не называл это метапрограммой в смысле, обычно предназначенным для таких вещей, как система шаблонов С++ или макросы Lisp. По-видимому, это просто форма генерации кода, которая вводит некоторую функциональность в существующие классы на этапе компиляции (ее можно рассматривать как предшественник своего рода стиля ориентированного на перспективу программирования в настоящее время в моде или прототипных объектных систем на таких языках, как JavaScripts
В качестве примера крайней длины вы можете взять это на С++, Boost MPL, tutorial показывает вам, как получить:
Размерные типы (Единицы измерения)
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
));
Ответ 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;
В основном вы используете шаблоны для вычисления факториала.
Более практичным примером, который я недавно видел, была объектная модель, основанная на таблицах БД, которые использовали шаблонные классы для моделирования отношений внешних ключей в базовых таблицах.
Ответ 5
Другой пример: в этом случае метапрограммирование tecnique используется для получения значения произвольной точности 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 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;
}
Ответ 6
Следующий пример снят с отличной книги С++ Templates - полное руководство.
#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";
}
Точка этого кода состоит в том, что рекурсивный расчет 7-й степени 3 выполняется во время компиляции, а не времени выполнения. Таким образом, он чрезвычайно эффективен с точки зрения производительности во время выполнения за счет более медленной компиляции.
Это полезно? В этом примере, вероятно, нет. Но есть проблемы, когда выполнение вычислений во время компиляции может быть преимуществом.
Ответ 7
Трудно сказать, что такое мета-программирование на С++. Все больше и больше я чувствую, что это похоже на введение "типов" в качестве переменных, как это имеет место в функциональном программировании. Это делает возможным декларативное программирование на С++.
Проще показать примеры.
Один из моих фаворитов - это "трюк" (или шаблон:)), чтобы сгруппировать многократно вложенные блоки 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::variant
в и из QVariant
.
Так как boost::variant
имеет механизм посещения O (1), направление boost::variant
to QVariant
является почти тривиальным.
Однако QVariant
не имеет механизма посещений, поэтому, чтобы преобразовать его в boost::variant
, вам нужно выполнить итерацию по типам mpl::list
типов, которые могут выполняться для конкретного экземпляра boost::variant
, и для каждого типа задайте QVariant
, содержит ли он этот тип, и если да, извлеките значение и верните его в boost::variant
. Это довольно весело, вы должны попробовать:)
Ответ 9
QtMetaObject в основном реализует отражение (Reflection) и является одной из основных форм метапрограммирования, на самом деле достаточно сильной. Он похож на отражение Java, и он также широко используется в динамических языках (Python, Ruby, PHP...). Это более читаемо, чем шаблоны, но оба имеют свои плюсы и минусы.
Ответ 10
Это простое "вычисление значений" по линиям Factorial. Тем не менее, это тот, который вы гораздо чаще используете в своем коде.
Макрос CT_NEXTPOWEROFTWO2 (VAL) использует метапрограммирование шаблонов для вычисления следующей мощности, равной двум или больше значения для значений, известных во время компиляции.
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