¿C++ soporta contadores de tiempo de compilación?


Con el propósito de introspección, a veces he querido asignar automáticamente números de serie a los tipos, o algo similar.

Desafortunadamente, la metaprogramación de plantillas es esencialmente un lenguaje funcional, y como tal carece de variables globales o estado modificable que implementaría tal contador.

¿O lo es?


Código de ejemplo a petición:

#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"
}
Author: Potatoswatter, 2011-05-29

7 answers

Bueno yes sí, la metaprogramación de plantillas carece de efectos secundarios como se pretende. Me engañó 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 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 ) > )

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 de C++11

Aquí hay una versión actualizada usando 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 completamente 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 alcance de múltiples espacios de nombres.

 44
Author: Potatoswatter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-19 09:20:08

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

 20
Author: Josh Matthews,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-29 08:17:52

Estuve pensando en resolver este problema durante bastante tiempo, y se me ocurrió una solución muy corta: limpia. Al menos merezco un voto positivo para probar esto. :))

El siguiente código de biblioteca logra la funcionalidad de nivel de espacio de nombres. es decir, tengo éxito en implementar counter_read y counter_inc; pero no la counter_inc_t (que se incrementa dentro de la función porque template las clases no se permiten 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 utiliza metaprogramación de plantillas y aprovecha la __LINE__ macro. Vea el resultado para el código de su respuesta.

 17
Author: iammilind,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-06-02 05:08:41

Usted podría utilizar BOOST_PP_COUNTER de Boost.Preprocesador.

Ventaja: funciona incluso para macros

Desventaja: solo hay un "tipo de contador" para todo el programa, pero el mecanismo puede ser reimplementado para contadores dedicados

 6
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-05-29 10:29:48

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

Esto usa la recursión de la función constexpr para contar el número de funciones Highest declaradas sin plantilla. __COUNTER__ se utiliza como un mecanismo generacional para evitar que nuevas declaraciones de Highest hagan auto recursión.

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

256 contador de nivel

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

Aplicació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

250 * 250 nivel contador

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 ser reducido a alrededor de 250 para los límites de recursión de tiempo de compilación del compilador. Sólo tenga en cuenta, esto tomó mucho más tiempo para compilar para mí.

Aplicació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);
 4
Author: rendaw,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:46:58

Dado que compartir es cuidar y pasé unas horas jugueteando con el ejemplo base este lado proporciona que voy a publicar mi solución también.

La versión enlazada 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 ha alcanzado una cuenta de más de unos pocos cientos es enorme.

Mediante la implementación de búsqueda binaria para detectar si un el indicador para un contador ya se ha establecido o no, es posible aumentar masivamente el conteo 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 ejemplo al final: (Excepto clang. Véanse los 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;
}
 3
Author: Chartas,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-09 17:10:59

Desafortunadamente, la metaprogramación de plantillas es esencialmente lenguaje, y como tal carece de variables globales o estado modificable que implementar un contador.

¿O lo es?

C++ permite 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 ID único interno int para cada instancia de plantilla. Ver v1 solución para el contador implementado con plantilla metaprogramación utilizando el encadenamiento de ID asignados y v2 para el segundo caso de uso. Ambas soluciones son respuestas para "¿Cómo puedo generar identificadores de tipos únicos densos en tiempo de compilación?". Pero la tarea tiene un requisito importante sobre el único asignador de ID.

 0
Author: Aleksey F.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:17:40