c++ templates metaprogramming state

¿Admite C++ los contadores de tiempo de compilación?



templates metaprogramming (7)

A los efectos de la introspección, a veces he querido asignar automáticamente números de serie a tipos, o algo similar.

Lamentablemente, la metaprogramación de plantillas es esencialmente un lenguaje funcional y, como tal, carece de variables globales o estados modificables que implementarían dicho contador.

¿O es eso?

Código de ejemplo por solicitud:

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


Lamentablemente, la metaprogramación de plantillas es esencialmente un lenguaje funcional y, como tal, carece de variables globales o estados modificables que implementarían dicho contador.

¿O es eso?

C ++ permite los contadores de tiempo de compilación (es decir, sin __COUNTER__ , __LINE__ u otros enfoques propuestos aquí anteriormente), así como la asignación y definición de la identificación interna int interna para cada instancia de plantilla. Vea la solución v1 para el contador implementado con la metaprogramación de la plantilla mediante el encadenamiento de las ID asignadas y v2 para el segundo caso de uso. Ambas soluciones son respuestas para "¿Cómo puedo generar identificadores de tipo único denso en tiempo de compilación?" . Pero la tarea tiene un requisito importante sobre el único asignador de ID.


Aquí hay otra implementación alternativa. https://.com/a/6174263/1190123 es probablemente mejor, pero incluso después de trabajar manualmente a través de un par de incrementos en papel, todavía no entiendo muy bien las matemáticas / el filtrado.

Utiliza la recursión de función constexpr para contar el número de funciones Highest declaradas que no son plantillas. __COUNTER__ se usa como un mecanismo generacional para evitar que nuevas declaraciones de lo Highest hagan autorrecurrencia.

Esto solo compila en clang para mí (3.3). No estoy seguro de que cumpla, pero tengo esperanza. g ++ 4.8 falla debido a alguna característica no implementada (según el error). El compilador 13 de Intel también falla, debido a un error de constexpr.

Contador de 256 niveles

El recuento máximo por contador es 250 (CounterLimit). CounterLimit se puede aumentar a 256 a menos que implemente las cosas de LCount a continuación.

Implementación

#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; / }

Pruebas

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

Salida

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

Contador de nivel 250 * 250

Si desea valores superiores a 256, creo que puede combinar contadores. Hice 250 * 250 (aunque realmente no probé contando más allá de 2). CounterLimit tiene que reducirse a alrededor de 250 para los límites de recursión en tiempo de compilación del compilador. Solo para observar, esto tomó mucho más tiempo para compilar para mí.

Implementación

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; / }

Pruebas

struct Counter3 {}; constexpr unsigned int Read7 = GetLCount(Counter3); IncrementLCount(Counter3); constexpr unsigned int Read8 = GetLCount(Counter3);


Bueno ... sí, la metaprogramación de plantillas carece de los efectos secundarios que se pretende. Fui engañado por un error en versiones anteriores de GCC y una redacción poco clara en el estándar para creer que todas esas características eran posibles.

Sin embargo, al menos la funcionalidad de ámbito de espacio de nombres se puede lograr con poco uso de plantillas. La búsqueda de funciones puede extraer el estado numérico del conjunto de funciones declaradas, como se muestra a continuación.

Código de la biblioteca:

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 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 ) > )

Demostración rápida ( verlo correr ):

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

Actualización C ++ 11

Aquí hay una versión actualizada que usa C ++ 11 constexpr en lugar de 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

Las declaraciones deben colocarse dentro de un espacio de nombres, y todos los nombres utilizados en las macros, excepto counter_crumb deben estar totalmente calificados. La plantilla counter_crumb se encuentra a través de la asociación ADL con el tipo constant_index .

La macro COUNTER_LINK_NAMESPACE se puede usar para incrementar un contador en el ámbito de varios espacios de nombres.


Como el intercambio es cuidado y pasé algunas horas jugueteando con el ejemplo base de this lado, también voy a publicar mi solución.

La versión vinculada en el artículo tiene dos desventajas principales. El número máximo que puede contar también es muy bajo, debido a la profundidad máxima de recursión (generalmente alrededor de 256). Y el tiempo que se tarda en compilar tan pronto como se alcanza un recuento de más de unos cientos es enorme.

Al implementar la búsqueda binaria para detectar si ya se ha establecido un indicador para un contador, es posible aumentar enormemente el recuento máximo (controlable a través de MAX_DEPTH) y también mejorar el tiempo de compilación al mismo tiempo. =)

Ejemplo de uso:

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ódigo completamente funcional con un ejemplo al final: (Excepto por clang. Ver comentarios).

// 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; }


Creo que tanto MSVC como GCC admiten un __COUNTER__ preprocesador __COUNTER__ que tiene un valor creciente monótonamente sustituido en su lugar.


Estaba pensando en resolver este problema por bastante tiempo, y he encontrado una solución de limpieza muy corta. Por lo menos, merezco un voto para probarlo. :))

El siguiente código de biblioteca logra la funcionalidad del nivel de espacio de nombres. es decir, tengo éxito en implementar counter_read y counter_inc ; pero no el counter_inc_t (que se incrementa dentro de la función porque template clases de template no están permitidas dentro de la función)

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

Esta técnica usa meta-programación de plantillas y aprovecha la macro __LINE__ . Vea el resultado del código de su respuesta.


Puede usar BOOST_PP_COUNTER de Boost.Preprocessor.

Ventaja: funciona incluso para macros

Desventaja: solo hay un "tipo contador" para todo el programa, pero el mecanismo se puede volver a implementar para contadores dedicados