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

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

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

или это?


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

#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 ответов


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

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


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


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

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


можно использовать BOOST_PP_COUNTER от Boost.Препроцессор.

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

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


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

это использует рекурсию функции 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);

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

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

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

или это?

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