Поддерживает ли 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
декларации должны быть помещены в пространство имен, и все имена, используемые в макросах, кроме
counter_crumb
должен быть полным. Элементcounter_crumb
шаблон найден через ассоциацию ADL сconstant_index
тип.The
COUNTER_LINK_NAMESPACE
макрос может использоваться для увеличения одного счетчика в области нескольких пространств имен.
Я считаю, что MSVC и GCC поддерживают a
__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
от наддува.Препроцессор.преимущество: он работает даже для макросов
недостаток: существует только один "вид счетчика" для всей программы, но механизм может быть переопределен для выделенных счетчиков
вот еще одна альтернативная реализация. 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 решение для счетчика реализовано с шаблоном метапрограммирования с использованием цепочки выделенных идентификаторов и v2 для второго варианта использования. Оба решения являются ответами для " как я могу генерировать плотные уникальные идентификаторы типов во время компиляции?". Но задача имеет важное требование о единственном распределителе идентификаторов.
Comments