web-dev-qa-db-fra.com

C ++ prend-il en charge les compteurs au moment de la compilation?

À des fins d'introspection, j'ai parfois voulu attribuer automatiquement des numéros de série à des types, ou quelque chose de similaire.

Malheureusement, la métaprogrammation de modèle est essentiellement un langage fonctionnel, et en tant que tel, il manque des variables globales ou un état modifiable qui implémenterait un tel compteur.

Ou est-ce?


Exemple de code sur demande:

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

Eh bien… oui, la métaprogrammation du modèle manque d'effets secondaires comme prévu. J'ai été induit en erreur par un bug dans les anciennes versions de GCC et un libellé peu clair dans la norme pour croire que toutes ces fonctionnalités étaient possibles.

Cependant, au moins la fonctionnalité de portée d'espace de noms peut être obtenue avec peu d'utilisation de modèles. La recherche de fonction peut extraire l'état numérique de l'ensemble des fonctions déclarées, comme illustré ci-dessous.

Code de bibliothèque:

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

Démo rapide ( voir exécuter ):

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

Mise à jour C++ 11

Voici une version mise à jour utilisant C++ 11 constexpr à la place 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

Les déclarations doivent être placées dans un espace de noms, et tous les noms utilisés dans les macros, sauf counter_crumb devrait être pleinement qualifié. Le counter_crumb le modèle est trouvé via l'association ADL avec le constant_index type.

Le COUNTER_LINK_NAMESPACE macro peut être utilisée pour incrémenter un compteur dans le cadre de plusieurs espaces de noms.

45
Potatoswatter

Je crois que MSVC et GCC prennent en charge un __COUNTER__ jeton de préprocesseur qui a une valeur croissante monotone substituée à sa place.

21
Josh Matthews

Je pensais résoudre ce problème depuis un certain temps et j'ai trouvé une solution très courte. Au moins, je mérite un vote positif pour l'essayer. :))

Le code de bibliothèque suivant atteint la fonctionnalité de niveau d'espace de noms. c'est-à-dire que j'ai réussi à mettre en œuvre counter_read et counter_inc; mais pas le counter_inc_t (qui est incrémenté à l'intérieur de la fonction car template les classes ne sont pas autorisées à l'intérieur de la fonction)

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

Cette technique utilise la méta-programmation du modèle et exploite le __LINE__ macro. Voir le résultat pour le code de votre réponse.

18
iammilind

Puisque le partage est bienveillant et j'ai passé quelques heures à tripoter l'exemple de base ce côté fournit que je vais également publier ma solution.

La version liée à l'article présente deux inconvénients majeurs. Le nombre maximal qu'il peut également compter est très faible, en raison de la profondeur de récursivité maximale (généralement environ 256). Et le temps qu'il faut pour compiler dès qu'un compte de plus de quelques centaines a été atteint est énorme.

En implémentant la recherche binaire pour détecter si un indicateur pour un compteur a déjà été défini ou non, il est possible d'augmenter massivement le nombre max (contrôlable via MAX_DEPTH) et également d'améliorer le temps de compilation en même temps. =)

Exemple d'utilisation:

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

Code entièrement fonctionnel avec un exemple à la fin: (Sauf pour clang. Voir commentaires.)

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

Vous pouvez utiliser BOOST_PP_COUNTER de Boost.Preprocessor.

Avantage: cela fonctionne même pour les macros

Inconvénient: il n'y a qu'un seul "type de compteur" pour l'ensemble du programme, mais le mécanisme peut être réimplémenté pour des compteurs dédiés

6
Matthieu M.

Voici une autre implémentation alternative. https://stackoverflow.com/a/6174263/119012 est probablement mieux, mais même après avoir travaillé manuellement quelques incréments sur du papier, je ne comprends toujours pas très bien les mathématiques/filtrage.

Cela utilise la récursion de la fonction constexpr pour compter le nombre de fonctions Highest déclarées non-modèle. __COUNTER__ est utilisé comme mécanisme générationnel pour empêcher les nouvelles déclarations de Highest de faire une auto-récursivité.

Cela ne compile que pour moi (3.3). Je ne suis pas sûr qu'il soit conforme, mais j'espère. g ++ 4.8 échoue en raison d'une fonctionnalité non implémentée (selon l'erreur). Le compilateur Intel 13 échoue également, en raison d'un bogue constexpr.

Compteur de 256 niveaux

Le nombre maximum par compteur est de 250 (CounterLimit). CounterLimit peut être augmenté à 256 à moins que vous n'implémentiez les éléments LCount ci-dessous.

La mise en oeuvre

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

Essai

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

Production

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

Compteur de niveau 250 * 250

Si vous voulez des valeurs supérieures à 256, je pense que vous pouvez combiner des compteurs. J'ai fait 250 * 250 (bien que je n'aie pas vraiment testé en comptant les 2 derniers). CounterLimit doit être abaissé à environ 250 pour les limites de récursion du temps de compilation du compilateur. Juste pour noter, cela a pris beaucoup plus de temps à compiler pour moi.

La mise en oeuvre

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

Essai

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

Malheureusement, la métaprogrammation de modèle est essentiellement un langage fonctionnel, et en tant que tel, il manque des variables globales ou un état modifiable qui implémenterait un tel compteur.

Ou est-ce?

C++ permet de compiler des compteurs de temps (c'est-à-dire sans __COUNTER__, __LINE__ ou d'autres approches proposées ci-dessus) ainsi que l'allocation et la définition de l'ID unique interne interne pour chaque instance de modèle. Voir v1 solution pour le compteur implémenté avec la métaprogrammation de modèle en utilisant les ID alloués en chaîne et v2 pour le deuxième cas d'utilisation. Les deux solutions sont des réponses pour "Comment puis-je générer des ID de type uniques denses au moment de la compilation?" . Mais la tâche a une exigence importante concernant le seul allocateur d'ID.

1
Aleksey F.

J'ai moi-même traversé tout cela et j'ai finalement trouvé une solution qui semble être conforme aux normes (au moment où j'écris ceci) et qui fonctionne avec gcc, clang, msvc et icc, dans toutes leurs versions récentes et dans la plupart des anciens.

J'ai parlé de l'ensemble du processus dans un autre article ici: compteurs de temps de compilation C++, revisités .

J'ai ensuite emballé la solution dans un fameta::counter classe qui résout quelques bizarreries restantes.

Vous pouvez le trouver sur github .

0
Fabio A.