C ++ Template Meta Programming: Compile-time tracer / counter [duplicate]

В этом контексте я поеду на Inner Join. Если бы я использовал contains, он бы итерации 6 раз, несмотря на тот факт, что существует только одно совпадение.

var desiredNames = new[] { "Pankaj", "Garg" }; 

var people = new[]  
{  
    new { FirstName="Pankaj", Surname="Garg" },  
    new { FirstName="Marc", Surname="Gravell" },  
    new { FirstName="Jeff", Surname="Atwood" }  
}; 

var records = (from p in people join filtered in desiredNames on p.FirstName equals filtered  select p.FirstName).ToList(); 

Недостатки Contains

Предположим, у меня есть два объекта списка.

List 1      List 2
  1           12
  2            7
  3            8
  4           98
  5            9
  6           10
  7            6

Используя Contains, он будет искать каждый элемент List 1 в списке 2, что означает, что итерация произойдет 49 раз !!!

54
задан Potatoswatter 29 May 2011 в 08:47
поделиться

7 ответов

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

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

Код библиотеки:

template< size_t n > // This type returns a number through function lookup.
struct cn // The function returns cn<n>.
    { char data[ n + 1 ]; }; // The caller uses (sizeof fn() - 1).

template< typename id, size_t n, size_t acc >
cn< acc > seen( id, cn< n >, cn< acc > ); // Default fallback case.

/* Evaluate the counter by finding the last defined overload.
   Each function, when defined, alters the lookup sequence for lower-order
   functions. */
#define counter_read( id ) \
( sizeof seen( id(), cn< 1 >(), cn< \
( sizeof seen( id(), cn< 2 >(), cn< \
( sizeof seen( id(), cn< 4 >(), cn< \
( sizeof seen( id(), cn< 8 >(), cn< \
( sizeof seen( id(), cn< 16 >(), cn< \
( sizeof seen( id(), cn< 32 >(), cn< 0 \
/* Add more as desired; trimmed for Stack Overflow code block. */ \
                      >() ).data - 1 ) \
                      >() ).data - 1 ) \
                      >() ).data - 1 ) \
                      >() ).data - 1 ) \
                      >() ).data - 1 ) \
                      >() ).data - 1 )

/* Define a single new function with place-value equal to the bit flipped to 1
   by the increment operation.
   This is the lowest-magnitude function yet undefined in the current context
   of defined higher-magnitude functions. */
#define counter_inc( id ) \
cn< counter_read( id ) + 1 > \
seen( id, cn< ( counter_read( id ) + 1 ) & ~ counter_read( id ) >, \
          cn< ( counter_read( id ) + 1 ) & counter_read( id ) > )

Быстрая демонстрация ( см. Прогон ):

struct my_cnt {};

int const a = counter_read( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );
counter_inc( my_cnt );

int const b = counter_read( my_cnt );

counter_inc( my_cnt );

#include <iostream>

int main() {
    std::cout << a << ' ' << b << '\n';

    std::cout << counter_read( my_cnt ) << '\n';
}

C ++ 11 Обновление

Ниже приведена версия, использующая C ++ 11 constexpr вместо sizeof.

#define COUNTER_READ_CRUMB( TAG, RANK, ACC ) counter_crumb( TAG(), constant_index< RANK >(), constant_index< ACC >() )
#define COUNTER_READ( TAG ) COUNTER_READ_CRUMB( TAG, 1, COUNTER_READ_CRUMB( TAG, 2, COUNTER_READ_CRUMB( TAG, 4, COUNTER_READ_CRUMB( TAG, 8, \
    COUNTER_READ_CRUMB( TAG, 16, COUNTER_READ_CRUMB( TAG, 32, COUNTER_READ_CRUMB( TAG, 64, COUNTER_READ_CRUMB( TAG, 128, 0 ) ) ) ) ) ) ) )

#define COUNTER_INC( TAG ) \
constexpr \
constant_index< COUNTER_READ( TAG ) + 1 > \
counter_crumb( TAG, constant_index< ( COUNTER_READ( TAG ) + 1 ) & ~ COUNTER_READ( TAG ) >, \
                                                constant_index< ( COUNTER_READ( TAG ) + 1 ) & COUNTER_READ( TAG ) > ) { return {}; }

#define COUNTER_LINK_NAMESPACE( NS ) using NS::counter_crumb;

template< std::size_t n >
struct constant_index : std::integral_constant< std::size_t, n > {};

template< typename id, std::size_t rank, std::size_t acc >
constexpr constant_index< acc > counter_crumb( id, constant_index< rank >, constant_index< acc > ) { return {}; } // found by ADL via constant_index

http://ideone.com/yp19oo

Объявления должны помещаться внутри пространства имен, а все имена, используемые в макросах, кроме counter_crumb, должны быть полностью квалифицированный. Шаблон counter_crumb найден через ассоциацию ADL с типом constant_index.

Макрос COUNTER_LINK_NAMESPACE можно использовать для увеличения одного счетчика в области нескольких пространств имен.

44
ответ дан Potatoswatter 18 August 2018 в 23:35
поделиться
  • 1
    Ссылка на ваш первый код, запущенный в сети, кажется недействительной. – GingerPlusPlus 21 November 2014 в 20:05
  • 2
    @GingerPlusPlus Спасибо, я сообщу IDEone. В любом случае результат будет таким же, как и второй код. – Potatoswatter 21 November 2014 в 23:27
  • 3
    cn<N> может быть дополнена по усмотрению компилятора. Таким образом, sizeof( cn<N> ) может быть любым значением & gt; = N. Требуется использовать sizeof( cn<N>::data ). – Cheers and hth. - Alf 8 April 2016 в 15:46
  • 4
    – Cheers and hth. - Alf 8 April 2016 в 15:49
  • 5
    @ Cheersandhth.-Alf Использование отдельных идентификаторов (в отличие от одного глобального __COUNTER__) устраняет проблему заказа включения. Если нумерация файлов заголовков является целью, то пользователь должен знать о согласованности между TU. – Potatoswatter 9 April 2016 в 04:01

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

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

Реализовав двоичный поиск, чтобы определить, установлен ли флаг для счетчика или нет, можно увеличить максимальное количество (контролируемое через MAX_DEPTH), а также улучшить время компиляции одновременно. =)

Пример использования:

static constexpr int a = counter_id();
static constexpr int b = counter_id();
static constexpr int c = counter_id();

#include <iostream>

int main () {
    std::cout << "Value a: " << a << std::endl;
    std::cout << "Value b: " << b << std::endl;
    std::cout << "Value c: " << c << std::endl;
}

Полностью рабочий код с примером в конце: (За исключением clang. См. комментарии.)

// Number of Bits our counter is using. Lower number faster compile time,
// but less distinct values. With 16 we have 2^16 distinct values.
#define MAX_DEPTH 16

// Used for counting.
template<int N>
struct flag {
    friend constexpr int adl_flag(flag<N>);
};

// Used for noting how far down in the binary tree we are.
// depth<0> equales leaf nodes. depth<MAX_DEPTH> equals root node.
template<int N> struct depth {};

// Creating an instance of this struct marks the flag<N> as used.
template<int N>
struct mark {
    friend constexpr int adl_flag (flag<N>) {
        return N;
    }

    static constexpr int value = N;
};

// Heart of the expression. The first two functions are for inner nodes and
// the next two for termination at leaf nodes.

// char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1] is valid if flag<N> exists.
template <int D, int N, class = char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1]>
int constexpr binary_search_flag(int,  depth<D>, flag<N>,
        int next_flag = binary_search_flag(0, depth<D-1>(), flag<N + (1 << (D - 1))>())) {
    return next_flag;
}

template <int D, int N>
int constexpr binary_search_flag(float, depth<D>, flag<N>,
        int next_flag = binary_search_flag(0, depth<D-1>(), flag<N - (1 << (D - 1))>())) {
    return next_flag;
}

template <int N, class = char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1]>
int constexpr binary_search_flag(int,   depth<0>, flag<N>) {
    return N + 1;
}

template <int N>
int constexpr binary_search_flag(float, depth<0>, flag<N>) {
    return N;
}

// The actual expression to call for increasing the count.
template<int next_flag = binary_search_flag(0, depth<MAX_DEPTH-1>(),
        flag<(1 << (MAX_DEPTH-1))>())>
int constexpr counter_id(int value = mark<next_flag>::value) {
    return value;
}

static constexpr int a = counter_id();
static constexpr int b = counter_id();
static constexpr int c = counter_id();

#include <iostream>

int main () {
    std::cout << "Value a: " << a << std::endl;
    std::cout << "Value b: " << b << std::endl;
    std::cout << "Value c: " << c << std::endl;
}
3
ответ дан Chartas 18 August 2018 в 23:35
поделиться
  • 1
    Это не работает для меня. Я использую Clang 4.0. – user2023370 9 August 2017 в 12:55
  • 2
    Ты прав. Я просто тестировал его с помощью vc ++, gcc и clang. Первые две работы, но clang нет. Причина этого заключается в том, что выражение, используемое для проверки того, определено ли adl_flag, не работает для clang. (Этот: class = char[noexcept( adl_flag(flag<N>()) ) ? +1 : -1]) Если вы можете найти тот, который правильно возвращает тип, только если adl_flag(flag<N>) уже определен, это будет работать. – Chartas 9 August 2017 в 16:17
  • 3
    Попробуйте найти здесь внизу для исправления clang. Это, вероятно, немного больше, чтобы включить его в код, но он должен быть выполнимым. – Chartas 9 August 2017 в 16:28
  • 4
    Только ответ, который не использует макросы – WorldSEnder 29 October 2017 в 05:46
  • 5
    Примечание для читателя: CWG выразила желание устранить лазейку друга, которая позволяет этому работать. Он не может быть надежным (и не всегда работает на всех компиляторах). Подробнее см. Здесь: b.atch.se/posts/constexpr-meta-container/#conclusion-wg21 – Jon Harper 20 April 2018 в 09:23

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

Или это?

C ++ позволяет скомпилировать счетчики времени (т. е. без __COUNTER__, __LINE__ или других подходов, предложенных здесь ранее), а также для выделения и определения внутреннего уникального идентификатора int для каждого экземпляра шаблона. См. Решение v1 для счетчика, реализованного с метапрограммой шаблона с использованием цепочки выделенных идентификаторов и v2 для второго варианта использования. Оба решения являются ответами на «Как я могу генерировать плотные уникальные идентификаторы типов во время компиляции?» . Но задача имеет важное требование к единственному идентификатору.

4
ответ дан Community 18 August 2018 в 23:35
поделиться
  • 1
    Обратите внимание, что предел применяется к числу раз, когда счетчик может быть оценен, а не его максимальное значение. Извините, мне следовало бы объяснить математику, которую я использовал. И вообще, как работает моя реализация ... это скорее связано. Но мой - O (логарифмическое предельное значение) для чтения и записи, тогда как это, как представляется, O (ограничение доступа). – Potatoswatter 8 August 2013 в 07:37
  • 2
    Обратите внимание, что вы можете использовать __VA_ARGS__ и переменные макросы, чтобы передать , в качестве аргумента макроса, обрезая COMMA. – Potatoswatter 8 August 2013 в 07:41
  • 3
    Спасибо за __VA_ARGS__ отзыв! Я не хотел критиковать ваш ответ; даже если вы объяснили это, я не уверен, что у меня есть необходимые умственные способности. Если бы вы добавили еще несколько объяснений, я бы внимательно прочитал его. – rendaw 8 August 2013 в 12:59
  • 4
    Что касается сложности, я думал, что это O (предельное значение) ... Если я правильно понимаю свой код (lol), он рекурсии CounterLimit в GetCount и 3 * CounterLimit в GetLCount. __COUNTER__ должен был только изменить видимость функции и переустановить шаблон. Я только что проверил, и CounterLimit может быть 250 без каких-либо проблем, поэтому я думаю, что изначально неправильно оценил рекурсию. – rendaw 8 August 2013 в 13:04
  • 5
    Я попробовал файл с IncrementLCount 32000 раз, и clang был убит ядром (из памяти) примерно через 20 минут (4 ГБ оперативной памяти, + 2 ГБ swap). – rendaw 8 August 2013 в 14:02

Я собирался решить эту проблему довольно давно и придумал очень короткое чистое решение. По крайней мере, я заслужил один взлет, чтобы попробовать это. :))

После кода библиотеки достигается функциональность уровня пространства имен. то есть мне удастся реализовать counter_read и counter_inc; но не counter_inc_t (который добавляется внутри функции, потому что классы template не допускаются внутри функции)

template<unsigned int NUM> struct Counter { enum { value = Counter<NUM-1>::value }; };
template<> struct Counter<0> { enum { value = 0 }; };

#define counter_read Counter<__LINE__>::value
#define counter_inc template<> struct Counter<__LINE__> { enum { value = Counter<__LINE__-1>::value + 1}; }

Этот метод использует метапрограммирование шаблона и использует макрос __LINE__. См. результат для кода из вашего ответа.

17
ответ дан iammilind 18 August 2018 в 23:35
поделиться
  • 1
    Очень хорошо! Тем не менее, это накладывает уровень вложенности шаблонов для каждой строки источника, поэтому для больших файлов он, скорее всего, не будет компилироваться. – Potatoswatter 2 June 2011 в 08:31
  • 2
    Кроме того, он будет запутан, если будет использоваться два разных файла заголовка. (Но пространства имен могут использоваться, чтобы содержать ущерб.) – Potatoswatter 2 June 2011 в 09:17
  • 3
    1 & lt; 9 - только 512; v). См. ideone.com/dOXTG . Как видно из сообщения об ошибке, 512 - это самое высокое значение, которое гарантировано работает с этой версией этого компилятора. – Potatoswatter 2 June 2011 в 09:27
  • 4
    – iammilind 8 April 2016 в 15:17
  • 5
    @iammilind Он создает экземпляры шаблонов O (N), где N - длина исходного файла. Это неоптимально, хотя это может сработать. Максимальная глубина шаблона со временем увеличивается на любой данной платформе. – Potatoswatter 9 April 2016 в 04:10

Я считаю, что как MSVC, так и GCC поддерживают токен препроцессора __COUNTER__, который на его месте заменяет монотонно увеличивающееся значение.

20
ответ дан Josh Matthews 18 August 2018 в 23:35
поделиться
  • 1
    +1 приятный для новой информации – iammilind 2 June 2011 в 06:13
  • 2
    какое слово, monotonically – thecoshman 16 December 2011 в 14:04
  • 3
    Вы должны проверить виды красоты, которые приводят к таким словам, как duodecilliotonically, если я получу свои префиксы правильно ...: P – Luis Machuca 22 March 2012 в 05:35
  • 4
    Это наиболее распространенное решение, но 1. не является стандартным; 2. не может использоваться повторно - на единицу перевода есть только один счетчик; 3. не может быть прочитан без изменения. – Potatoswatter 16 June 2013 в 22:50

Вы можете использовать BOOST_PP_COUNTER из Boost.Preprocessor.

Преимущество: он работает даже для макросов

Недостаток: есть только один счетчик вид "для всей программы, но механизм может быть переопределен для выделенных счетчиков

6
ответ дан Matthieu M. 18 August 2018 в 23:35
поделиться
4
ответ дан Community 30 October 2018 в 11:18
поделиться
Другие вопросы по тегам:

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