Шаблонное Метапрограммирование - я все еще не получаю его :(

Мне понравились измеренные структуры переменной, которые Вы могли сделать:

typedef struct {
    unsigned int size;
    char buffer[1];
} tSizedBuffer;

tSizedBuffer *buff = (tSizedBuffer*)(malloc(sizeof(tSizedBuffer) + 99));

// can now refer to buff->buffer[0..99].

Также offsetof макрос, который находится теперь в ANSI C, но был частью колдовства в первый раз, я видел его. Это в основном использует операцию вычисления адреса (&) для нулевого указателя, переделанного как переменная структуры.

30
задан Lii 16 June 2018 в 09:12
поделиться

11 ответов

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

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

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

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

28
ответ дан 27 November 2019 в 23:17
поделиться

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

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

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,
    };
};

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

15
ответ дан 27 November 2019 в 23:17
поделиться

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

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

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

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

Для исправления указателей, чтобы их можно было использовать в качестве итераторов. Итератор должен предоставлять несколько определений типов, например 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 :: type .

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

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

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

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

9
ответ дан 27 November 2019 в 23:17
поделиться

Повторяю рекомендацию для Александреску Современный дизайн C ++ .

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

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

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

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

9
ответ дан 27 November 2019 в 23:17
поделиться

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

Бинарная константа / литерал C ++

template< unsigned long long N >
struct binary
{
  enum { value = (N % 10) + 2 * binary< N / 10 > :: value } ;
};
template<>
struct binary< 0 >
{
  enum { value = 0 } ;
};
3
ответ дан 27 November 2019 в 23:17
поделиться

The factorial example is about as useful for real-world TMP as "Hello, world!" is for common programming: It's there to show you a few useful techniques (recursion instead of iteration, "else-if-then" etc.) in a very simple, relatively easy to understand example that doesn't have much relevance for your every-day coding. (When was the last time you needed to write a program that emitted "Hello, world"?)

TMP is about executing algorithms at compile-time and this implies a few obvious advantages:

  • Since these algorithms failing means your code doesn't compile, failing algorithms never make it to your customer and thus can't fail at the customer's. For me, during the last decade this was the single-most important advantage that led me to introduce TMP into the code of the companies I worked for.
  • Since the result of executing template-meta programs is ordinary code that's then compiled by the compiler, all advantages of code generating algorithms (reduced redundancy etc.) apply.
  • Of course, since they are executed at compile-time, these algorithms won't need any run-time and will thus run faster. TMP is mostly about compile-time computing with a few, mostly small, inlined functions sprinkled in between, so compilers have ample opportunities to optimize the resulting code.

Of course, there's disadvantages, too:

  • The error messages can be horrible.
  • There's no debugging.
  • The code is often hard to read.

As always, you'll just have to weight the advantages against the disadvantages in every case.

As for a more useful example: Once you have grasped type lists and basic compile-time algorithms operating on them, you might understand the following:

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;

This is (slightly simplified) actual code I wrote a few weeks ago. It will pick the appropriate types from a type list, replacing the #ifdef orgies common in portable code. It doesn't need maintenance, works without adaption on every platform your code might need to get ported to, and emits a compile error if the current platform doesn't have the right type.

Another example is this:

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

Given a function f and a sequence of strings, this will dissect the function's signature, convert the strings from the sequence into the right types, and call the function with these objects. And it's mostly TMP inside.

5
ответ дан 27 November 2019 в 23:17
поделиться

TMP does not necessarily mean faster or more maintainable code. I used the boost spirit library to implement a simple SQL expression parser that builds an evaluation tree structure. While the development time was reduced since I had some familiarity with TMP and lambda, the learning curve is a brick wall for "C with classes" developers, and the performance is not as good as a traditional LEX/YACC.

I see Template Meta Programming as just another tool in my tool-belt. When it works for you use it, if it doesn't, use another tool.

3
ответ дан 27 November 2019 в 23:17
поделиться

Предлагаю вам прочитать Современный дизайн C ++ Андрея Александреску - это, вероятно, одна из лучших книг о реальном использовании метапрограммирования шаблонов C ++; и описывает множество проблем, для которых шаблоны C ++ являются отличным решением.

2
ответ дан 27 November 2019 в 23:17
поделиться

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

Это неплохое чтение:
http://www.artima.com/cppsource/codefeatures.html

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

3
ответ дан 27 November 2019 в 23:17
поделиться

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

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

Здесь

2
ответ дан 27 November 2019 в 23:17
поделиться

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

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

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

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

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

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

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

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

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

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

2
ответ дан 27 November 2019 в 23:17
поделиться
Другие вопросы по тегам:

Похожие вопросы: