Подтвердить что ты не робот

Template Metaprogramming - я до сих пор не понимаю:(

У меня проблема. Я не понимаю Template Metaprogramming.

Проблема в том, что я много читаю. Но мне это не имеет смысла:/

Факт nr.1: шаблон метапрограммирования быстрее

template <int N>
struct Factorial 
{
    enum { value = N * Factorial<N - 1>::value };
};

template <>
struct Factorial<0> 
{
    enum { value = 1 };
};

// Factorial<4>::value == 24
// Factorial<0>::value == 1
void foo()
{
    int x = Factorial<4>::value; // == 24
    int y = Factorial<0>::value; // == 1
}

так что эта метапрограмма быстрее... beacause of Constant Literal.
НО: Где в реальном мире у нас есть постоянные литералы?
Большинство программ, которые я использую, реагируют на ввод пользователя.

ФАКТ №. 2: шаблон метапрограммирования может улучшить лучшую ремонтопригодность.

Да. Факториальный пример может быть полезным... но когда дело доходит до сложных функций, я и большинство других программистов на C++ не могут читать функции. И параметры отладки плохие (или, по крайней мере, я не знаю, как отлаживать)

Где шаблон метапрограммирования имеет смысл?

4b9b3361

Ответ 1

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

При написании шаблонов для реалистичных целей, например в повседневных библиотеках, часто шаблон должен адаптировать то, что он делает, в зависимости от параметров типа, с которыми он создается. Это может стать довольно сложным, поскольку шаблон эффективно выбирает, какой код генерировать, условно. Это шаблон метапрограммирования; если шаблон должен зацикливаться (через рекурсию) и выбирать между альтернативами, он фактически похож на небольшую программу, выполняемую во время компиляции для генерации правильного кода.

Здесь действительно хороший учебник с страниц документации по повышению (фактически выписанный из блестящей книги, который стоит прочитать).

http://www.boost.org/doc/libs/1_39_0/libs/mpl/doc/tutorial/representing-dimensions.html

Ответ 2

Я использую мета-программирование шаблонов для операторов SWE для оптимизации тасования во время компиляции.

SSE swizzles ('shuffles') может быть замаскирован только как байтовый литерал (немедленное значение), поэтому мы создали класс шаблонов слияния маски, который объединяет маски во время компиляции, когда происходит несколько перетасовки:

template <unsigned target, unsigned mask>
struct _mask_merger
{
    enum
    {
        ROW0 = ((target >> (((mask >> 0) & 3) << 1)) & 3) << 0,
        ROW1 = ((target >> (((mask >> 2) & 3) << 1)) & 3) << 2,
        ROW2 = ((target >> (((mask >> 4) & 3) << 1)) & 3) << 4,
        ROW3 = ((target >> (((mask >> 6) & 3) << 1)) & 3) << 6,

        MASK = ROW0 | ROW1 | ROW2 | ROW3,
    };
};

Это работает и создает замечательный код без генерирования служебных данных кода и небольшого дополнительного времени компиляции.

Ответ 3

так что эта метапрограмма будет быстрее... because of Constant Literal. НО: Где в реальном мире у нас есть постоянные литералы? Большинство программ, которые я использую, реагируют на ввод пользователя.

Вот почему это почти никогда не использовалось для ценностей. Обычно он используется для типов. используя типы для вычисления и генерации новых типов.

Существует много реальных приложений, некоторые из которых вы уже знакомы, даже если вы этого не понимаете.

Один из моих любимых примеров - это итераторы. Они в основном разработаны только с общим программированием, да, но метапрограммирование шаблона полезно в одном месте, в частности:

Для исправления указателей, чтобы они могли использоваться как итераторы. Итератор должен выставить несколько typedef, например value_type. Указатели этого не делают.

Итак, код, такой как следующий (в основном идентичный тому, что вы найдете в Boost.Iterator)

template <typename T>
struct value_type {
  typedef typename T::value_type type;
};

template <typename T>
struct value_type<T*> {
  typedef T type;
};

- очень простая метапрограмма шаблона, но это очень полезно. Он позволяет вам получить тип значения любого типа итератора T, будь то указатель или класс, просто value_type<T>::type.

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

Трюки вроде boost::enable_if могут быть очень полезными. У вас есть перегрузка функции, которая должна быть включена только для определенного набора типов. Вместо определения перегрузки для каждого типа вы можете использовать метапрограммирование, чтобы указать условие и передать его в enable_if.

Эрвиккер уже упомянул еще один хороший пример, рамки для выражения физических единиц и размеров. Это позволяет вам выражать вычисления, например, с прикрепленными физическими единицами, и применяет тип результата. Умножение счетчиков на метры дает несколько квадратных метров. Метапрограммирование шаблонов может использоваться для автоматического создания правильного типа.

Но большую часть времени метапрограммирование шаблона используется (и полезно) в небольших отдельных случаях, в основном для сглаживания ударов и исключительных случаев, чтобы сделать набор типов одинаковым и однородным, позволяя вам использовать общее программирование больше эффективно

Ответ 4

Вторичная рекомендация для Alexandrescu Modern С++ Design.

Шаблоны действительно сияют, когда вы пишете библиотеку, в которой есть кусочки, которые могут быть собраны комбинаторно в "выбрать подход Foo, Bar и Baz", и вы ожидаете, что пользователи будут использовать эти фрагменты в той или иной форме, что фиксируется во время компиляции. Например, я соавтор библиотеки данных, которая использует метапрограммирование шаблонов, чтобы позволить программисту решить, что использовать DecisionType (классификация, ранжирование или регрессия), что InputType ожидать (float, ints, перечисляемые значения, что угодно) и что использовать KernelMethod (это инструмент для интеллектуального анализа данных). Затем мы реализовали несколько разных классов для каждой категории, чтобы было несколько десятков возможных комбинаций.

Реализация 60 отдельных классов для этого потребовала бы много раздражающего, трудно поддерживаемого дублирования кода. Метапрограммирование шаблонов означало, что мы могли бы реализовать каждую концепцию как блок кода и дать программисту простой интерфейс для создания комбинаций этих понятий во время компиляции.

Анализ размеров также является отличным примером, но другие люди это поняли.

Я также однажды написал несколько простых генераторов псевдослучайных чисел времени компиляции, чтобы общаться с людьми, но на самом деле это не значит IMO.

Ответ 5

Факторный пример примерно так же полезен для реального TMP как "Привет, мир!". для общего программирования: это там, чтобы показать вам несколько полезных методов (рекурсия вместо итерации, "else-if-then" и т.д.) в очень простом, относительно легко понятном примере, который не имеет большого значения для каждого -дневное кодирование. (Когда в последний раз вам нужно было написать программу, которая выбрала "Hello, world"?)

TMP - это выполнение алгоритмов во время компиляции, и это подразумевает несколько очевидных преимуществ:

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

Конечно, есть и недостатки:

  • Сообщения об ошибках могут быть ужасными.
  • Отладки нет.
  • Код часто трудно читать.

Как всегда, вам просто придется весить преимущества против недостатков в каждом случае.

Что касается более полезного примера: как только вы захватили списки типов и основные алгоритмы компиляции, работающие на них, вы можете понять следующее:

typedef 
    type_list_generator< signed char
                       , signed short
                       , signed int
                       , signed long
                       >::result_type
    signed_int_type_list;

typedef 
    type_list_find_if< signed_int_type_list
                     , exact_size_predicate<8>
                     >::result_type
    int8_t;

typedef 
    type_list_find_if< signed_int_type_list
                     , exact_size_predicate<16>
                     >::result_type
    int16_t;

typedef 
    type_list_find_if< signed_int_type_list
                     , exact_size_predicate<32>
                     >::result_type
    int32_t;

Это (слегка упрощенный) фактический код, который я написал несколько недель назад. Он выберет соответствующие типы из списка типов, заменив #ifdef оргии, распространенные в переносном коде. Он не нуждается в обслуживании, работает без адаптации на каждой платформе, для которой может потребоваться перенос кода, и испускает ошибку компиляции, если текущая платформа не имеет нужного типа.

Другой пример:

template< typename TFunc, typename TFwdIter >
typename func_traits<TFunc>::result_t callFunc(TFunc f, TFwdIter begin, TFwdIter end);

Учитывая функцию f и последовательность строк, это будет анализировать сигнатуру функции, преобразовать строки из последовательности в нужные типы и вызвать функцию с этими объектами. И это в основном TMP внутри.

Ответ 6

Скотт Мейерс работает над выполнением ограничений кода с помощью TMP.

Его неплохо читается:
http://www.artima.com/cppsource/codefeatures.html

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

Ответ 7

Здесь один тривиальный пример, двоичный преобразователь констант, из предыдущего вопроса здесь, в StackOverflow:

С++ двоичная константа/литерал

template< unsigned long long N >
struct binary
{
  enum { value = (N % 10) + 2 * binary< N / 10 > :: value } ;
};
template<>
struct binary< 0 >
{
  enum { value = 0 } ;
};

Ответ 8

TMP не обязательно означает более быстрый или более удобный код. Я использовал boost spirit для реализации простого синтаксического анализатора SQL, который строит структуру древа оценки. Хотя время разработки было сокращено, так как у меня было некоторое знакомство с TMP и лямбдой, кривая обучения - это кирпичная стена для разработчиков "C с классами", а производительность не так хороша, как традиционный LEX/YACC.

Я вижу Template Meta Programming как еще один инструмент в своем инструменте. Когда он работает, вы используете его, если нет, используйте другой инструмент.

Ответ 9

Я предлагаю вам прочитать Modern С++ Design от Andrei Alexandrescu - это, вероятно, одна из лучших книг по использованию шаблонов С++ в реальном мире метапрограммированием; и описывает множество проблем, которые С++-шаблоны являются отличным решением.

Ответ 10

TMP может использоваться из чего-либо вроде обеспечения правильности измерения (обеспечение того, что масса не может быть разделена по времени, но расстояние может быть разделено по времени и назначено переменной скорости) для оптимизации операций матрицы путем удаления временных объектов и слияния циклов, когда многие матрицы.

Ответ 11

Значения

'static const' также работают. И указатели на член. И не забывайте мир типов (явных и выведенных) как аргументы времени компиляции!

НО: Где в реальном мире у нас есть постоянные литералы?

Предположим, что у вас есть код, который должен запускаться как можно быстрее. Он содержит критический внутренний цикл вашего вычисления, связанного с CPU. Вы захотите немного увеличить размер исполняемого файла, чтобы сделать его быстрее. Это выглядит так:

double innerLoop(const bool b, const vector<double> & v)
{
    // some logic involving b

    for (vector::const_iterator it = v.begin; it != v.end(); ++it)
    {
        // significant logic involving b
    }

    // more logic involving b
    return ....
}

Детали не важны, но использование "b" широко распространено в реализации.

Теперь, с помощью шаблонов, вы можете немного реорганизовать его:

template <bool b> double innerLoop_B(vector<double> v) { ... same as before ... }
double innerLoop(const bool b, const vector<double> & v)
{ return b ? innerLoop_templ_B<true>(v) : innerLoop_templ_B<false>(v) ); }

Каждый раз, когда у вас есть относительно небольшой дискретный набор значений для параметра, вы можете автоматически создавать отдельные версии для них.

Рассмотрим возможности, когда 'b' основан на обнаружении ЦП. Вы можете запустить по-разному оптимизированный набор кода в зависимости от обнаружения во время выполнения. Все из одного и того же исходного кода, или вы можете специализировать некоторые функции для некоторых наборов значений.

В качестве конкретного примера я однажды увидел код, необходимый для слияния целочисленных координат. Система координат "a" была одной из двух разрешений (известная во время компиляции), а система координат "b" была одним из двух разных разрешений (также известных во время компиляции). Целевая система координат должна быть наименее общим кратным двух исходных систем координат. Библиотека использовалась для вычисления LCM во время компиляции и создания кода для различных возможностей.