Поддерживает ли С++ счетчики времени компиляции?

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

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

Или это?


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

#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"
}

Ответ 1

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

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

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

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 Qaru 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';
}

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

Вот обновленная версия, использующая С++ 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 может использоваться для увеличения одного счетчика в области нескольких пространств имен.

Ответ 2

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

Ответ 3

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

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

Ответ 4

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

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

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

Ответ 5

Здесь другая альтернативная реализация. fooobar.com/questions/78739/..., вероятно, лучше, но даже после ручной работы через пару шагов на бумаге я все еще не совсем понимаю математику/фильтрацию.

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

Это только компиляция для clang для меня (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);

Ответ 6

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

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

Ответ 7

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

Или это?

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