Поддерживает ли C ++ счетчики времени компиляции? - PullRequest
58 голосов
/ 29 мая 2011

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

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

Или это?


Пример кода по запросу:

#include <iostream>

int const a = counter_read;
counter_inc;
counter_inc;
counter_inc;
counter_inc;
counter_inc;

int const b = counter_read;

int main() {
    std::cout << a << ' ' << b << '\n'; // print "0 5"

    counter_inc_t();
    counter_inc_t();
    counter_inc_t();

    std::cout << counter_read << '\n'; // print "8"

    struct {
        counter_inc_t d1;
        char x[ counter_read ];
        counter_inc_t d2;
        char y[ counter_read ];
    } ls;

    std::cout << sizeof ls.x << ' ' << sizeof ls.y << '\n'; // print "9 10"
}

Ответы [ 7 ]

45 голосов
/ 30 мая 2011

Ну… да, шаблонному метапрограммированию не хватает побочных эффектов, как и предполагалось. Я был введен в заблуждение из-за ошибки в более старых версиях 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 можно использовать для увеличения значения одного счетчика в области нескольких пространств имен.

21 голосов
/ 29 мая 2011

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

18 голосов
/ 02 июня 2011

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

Следующий код библиотеки обеспечивает функциональность на уровне пространства имен. то есть я успешно внедрил 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__. См. результат для кода из вашего ответа.

6 голосов
/ 07 августа 2017

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

Версия, ссылка на которую есть в статье, имеет два основных недостатка. Максимальное число, которое он может сосчитать, очень мало из-за максимальной глубины рекурсии (обычно около 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;
}

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

// 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;
}
6 голосов
/ 29 мая 2011

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

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

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

4 голосов
/ 08 августа 2013

Вот еще одна альтернативная реализация. https://stackoverflow.com/a/6174263/1190123, вероятно, лучше, но даже после ручной обработки нескольких шагов на бумаге я все еще не совсем понимаю математику / фильтрацию.

Используется рекурсия функции constexpr для подсчета количества не шаблонных объявленных функций Highest. __COUNTER__ используется в качестве механизма генерации для предотвращения самостоятельной рекурсии новых объявлений Highest.

Это компилируется только для меня (3.3). Я не уверен, что это соответствует, но я надеюсь. В g ++ 4.8 происходит сбой из-за нереализованной функции (согласно ошибке). Компилятор Intel 13 также не работает из-за ошибки constexpr.

256 счетчик уровня

Максимальный счетчик на счетчик равен 250 (CounterLimit). CounterLimit может быть увеличен до 256, если вы не реализуете содержимое LCount ниже.

Осуществление

#include <iostream>
#include <type_traits>

constexpr unsigned int CounterLimit = 250;

template <unsigned int ValueArg> struct TemplateInt { constexpr static unsigned int Value = ValueArg; };

template <unsigned int GetID, typename, typename TagID>
constexpr unsigned int Highest(TagID, TemplateInt<0>)
{
    return 0;
}

template <unsigned int GetID, typename, typename TagID, unsigned int Index>
constexpr unsigned int Highest(TagID, TemplateInt<Index>)
{
    return Highest<GetID, void>(TagID(), TemplateInt<Index - 1>());
}

#define GetCount(...) \
Highest<__COUNTER__, void>(__VA_ARGS__(), TemplateInt<CounterLimit>())

#define IncrementCount(TagID) \
template <unsigned int GetID, typename = typename std::enable_if<(GetID > __COUNTER__ + 1)>::type> \
constexpr unsigned int Highest( \
    TagID, \
    TemplateInt<GetCount(TagID) + 1> Value) \
{ \
      return decltype(Value)::Value; \
}

Тестирование

struct Counter1 {};
struct Counter2 {};
constexpr unsigned int Read0 = GetCount(Counter1);
constexpr unsigned int Read1 = GetCount(Counter1);
IncrementCount(Counter1);
constexpr unsigned int Read2 = GetCount(Counter1);
IncrementCount(Counter1);
constexpr unsigned int Read3 = GetCount(Counter1);
IncrementCount(Counter1);
constexpr unsigned int Read4 = GetCount(Counter1);
IncrementCount(Counter1);
IncrementCount(Counter2);
constexpr unsigned int Read5 = GetCount(Counter1);
constexpr unsigned int Read6 = GetCount(Counter2);

int main(int, char**)
{
    std::cout << "Ending state 0: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<0>()) << std::endl;
    std::cout << "Ending state 1: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<1>()) << std::endl;
    std::cout << "Ending state 2: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<2>()) << std::endl;
    std::cout << "Ending state 3: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<3>()) << std::endl;
    std::cout << "Ending state 4: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<4>()) << std::endl;
    std::cout << "Ending state 5: " << Highest<__COUNTER__, void>(Counter1(), TemplateInt<5>()) << std::endl;
    std::cout << Read0 << std::endl;
    std::cout << Read1 << std::endl;
    std::cout << Read2 << std::endl;
    std::cout << Read3 << std::endl;
    std::cout << Read4 << std::endl;
    std::cout << Read5 << std::endl;
    std::cout << Read6 << std::endl;

    return 0;
}

выход

Ending state 0: 0
Ending state 1: 1
Ending state 2: 2
Ending state 3: 3
Ending state 4: 4
Ending state 5: 4
0
0
1
2
3
4
1

250 * 250 счетчик уровня

Если вы хотите более высокие значения, чем 256, я думаю, вы можете комбинировать счетчики. Я сделал 250 * 250 (хотя на самом деле я не проверял счет 2). CounterLimit должен быть снижен примерно до 250 для ограничения рекурсии времени компиляции. Просто чтобы заметить, это заняло у меня значительно больше времени для компиляции.

Осуществление

template <typename, unsigned int> struct ExtraCounter { };

template <unsigned int GetID, typename, typename TagID>
constexpr unsigned int LHighest(TagID)
{
    return Highest<GetID, void>(ExtraCounter<TagID, CounterLimit>(), TemplateInt<CounterLimit>()) * CounterLimit +
        Highest<GetID, void>(
            ExtraCounter<TagID, Highest<GetID, void>(ExtraCounter<TagID , CounterLimit>(), TemplateInt<CounterLimit>())>(),
            TemplateInt<CounterLimit>());
}
#define GetLCount(TagID) \
LHighest<__COUNTER__, void>(TagID())

#define LIncrementTag_(TagID) \
typename std::conditional< \
    GetCount(ExtraCounter<TagID, GetCount(ExtraCounter<TagID, CounterLimit>)>) == CounterLimit - 1, \
    ExtraCounter<TagID, CounterLimit>, \
    ExtraCounter<TagID, GetCount(ExtraCounter<TagID, CounterLimit>)>>::type
#define IncrementLCount(TagID) \
template <unsigned int GetID, typename = typename std::enable_if<(GetID > __COUNTER__ + 7)>::type> \
constexpr unsigned int Highest( \
    LIncrementTag_(TagID), \
    TemplateInt<GetCount(LIncrementTag_(TagID)) + 1> Value) \
{ \
      return decltype(Value)::Value; \
}

Тестирование

struct Counter3 {};
constexpr unsigned int Read7 = GetLCount(Counter3);
IncrementLCount(Counter3);
constexpr unsigned int Read8 = GetLCount(Counter3);
1 голос
/ 31 октября 2014

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

Или это?

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

...