it-roy-ru.com

Поддерживает ли 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"
}
58
Potatoswatter

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

45
Potatoswatter

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

20
Josh Matthews

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

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

18
iammilind

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

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

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

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

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

6
Matthieu M.

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

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

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

Либо это?

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

1
Aleksey F.