Что такое метапрограммирование? - PullRequest
15 голосов
/ 11 июня 2009

Что касается этого вопроса , кто-нибудь может объяснить и опубликовать пример кода метапрограммирования? Я погуглил этот термин, но не нашел примеров, чтобы убедить меня, что он может быть полезен для практического применения.

На той же ноте, является ли мета-объектная система Qt формой метапрограммирования?

JRH

Ответы [ 10 ]

25 голосов
/ 11 июня 2009

Большинство примеров до сих пор работали со значениями (вычисление цифр числа пи, факториал N или аналогичных), и это в значительной степени примеры из учебников, но в целом они не очень полезны. Просто сложно представить ситуацию, когда вам действительно нужен компилятор для вычисления 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 определяется как ...»

Второе - это то, что он используется для упрощения общего программирования. (Итераторы не были бы очень общей концепцией, если бы не работал для простейшего из всех примеров указатель на массив. Поэтому мы используем немного метапрограммирования, чтобы заполнить детали, необходимые для того, чтобы указатель считался действительным итератор).

Это довольно распространенный вариант использования метапрограммирования. Конечно, вы можете использовать его для широкого спектра других целей (шаблоны Expression - еще один часто используемый пример, предназначенный для оптимизации дорогостоящих вычислений, а Boost.Spirit - пример полного перебора и позволяющий вам определять собственный синтаксический анализатор при компиляции. время), но, вероятно, наиболее распространенное использование - это сгладить эти небольшие неровности и угловые случаи, которые в противном случае потребовали бы специальной обработки и сделали бы невозможным общее программирование.

8 голосов
/ 11 июня 2009

Хотя он большой (2000loc), я создал систему рефлексивных классов в c ++, которая не зависит от компилятора и включает в себя маршалинг объектов и метаданные, но не имеет затрат на хранение или штрафов за время доступа. Это жесткое метапрограммирование, которое используется в очень большой онлайн-игре для отображения игровых объектов для передачи по сети и отображения базы данных (ORM).

В любом случае компиляция занимает некоторое время, около 5 минут, но имеет преимущество, заключающееся в том, что для каждого объекта выполняется быстрая ручная настройка кода. Таким образом, он экономит много денег за счет сокращения значительного процессорного времени на наших серверах (загрузка процессора составляет 5% от того, что было раньше).

7 голосов
/ 11 июня 2009

Понятие полностью происходит от названия 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

Метафункции высшего порядка

дважды (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
));
5 голосов
/ 11 июня 2009

Вот типичный пример:

  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 голосов
/ 11 июня 2009

Другой пример: в этом случае метод метапрограммирования используется для получения значения 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;
}
2 голосов
/ 28 июля 2009

Единственный раз, когда мне нужно было использовать Boost.MPL в моей повседневной работе, это когда мне нужно было преобразовать boost::variant в QVariant.

Поскольку boost::variant имеет механизм посещения O (1), направление от boost::variant до QVariant почти тривиально.

Однако QVariant не имеет механизма посещения, поэтому для преобразования его в boost::variant необходимо выполнить итерацию по mpl::list типов, которые может содержать конкретная инстанциация boost::variant, и для каждого типа спросите QVariant, содержит ли он этот тип, и если да, извлеките значение и верните его в boost::variant. Это довольно весело, вы должны попробовать это:)

2 голосов
/ 11 июня 2009

Трудно сказать, что такое метапрограммирование 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;
}
2 голосов
/ 11 июня 2009

Следующий пример взят из превосходной книги Шаблоны C ++ - Полное руководство .

#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 происходит во время компиляции, а не во время выполнения. Таким образом, он чрезвычайно эффективен с точки зрения производительности во время выполнения за счет более медленной компиляции.

Это полезно? В этом примере, вероятно, нет. Но есть проблемы, когда выполнение вычислений во время компиляции может быть преимуществом.

0 голосов
/ 07 декабря 2011

Это простое «вычисление стоимости» по типу факториала. Тем не менее, это тот, который вы с большей вероятностью будете использовать в своем коде.

Макрос 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
0 голосов
/ 18 июня 2009

QtMetaObject в основном реализует отражение ( Reflection ) и IS , одну из основных форм метапрограммирования, на самом деле довольно мощную. Он похож на рефлексию Java и также широко используется в динамических языках (Python, Ruby, PHP ...). Это удобнее для чтения, чем шаблоны, но оба имеют свои плюсы и минусы.

...