80 votes

Le C++ supporte-t-il les compteurs de temps de compilation ?

Dans un but d'introspection, j'ai parfois voulu attribuer automatiquement des numéros de série aux types, ou quelque chose de similaire.

Malheureusement, la métaprogrammation par gabarit est essentiellement un langage fonctionnel et, en tant que tel, ne dispose pas de variables globales ou d'état modifiable qui permettraient de mettre en œuvre un tel compteur.

Ou pas ?


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

0 votes

Pouvez-vous donner un court exemple pour démontrer quelle est la question exacte ?

0 votes

N'est-il pas possible d'utiliser X<__LINE__> ? qui fournira un numéro unique (qui peut ne pas être un numéro de série) toujours dans le fichier donné.

0 votes

@iammilind : Cela ne fonctionne pas sur plusieurs en-têtes, et ne renvoie pas le même résultat de manière répétée lorsque l'unicité est atteinte. n'est pas souhaité. La solution du modèle est plus puissante. Voir la réponse.

51voto

Potatoswatter Points 70305

Eh bien oui, la métaprogrammation par gabarit n'a pas d'effets secondaires comme prévu. J'ai été induit en erreur par un bug dans les anciennes versions de GCC et une formulation peu claire dans la norme pour croire que toutes ces fonctionnalités étaient possibles.

Cependant, au moins la fonctionnalité de l'espace de noms peut être réalisée avec peu d'utilisation de modèles. La recherche de fonctions peut extraire un état numérique à partir de l'ensemble des fonctions déclarées, comme le montre la démonstration ci-dessous.

Code de la 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émonstration rapide ( le voir fonctionner ):

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 de 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, à l'exception de counter_crumb doit être pleinement qualifié. Le site counter_crumb est trouvé via l'association ADL avec le modèle constant_index type.

El COUNTER_LINK_NAMESPACE peut être utilisée pour incrémenter un compteur dans la portée de plusieurs espaces de noms.

0 votes

Le lien vers votre premier code en ligne semble invalidé.

0 votes

@GingerPlusPlus Merci, je vais prévenir IDEone. Le résultat est juste le même que le deuxième code, de toute façon.

0 votes

cn<N> peuvent être remplis à la discrétion du compilateur. Ainsi, sizeof( cn<N> ) peut être n'importe quelle valeur >= N. Doit utiliser sizeof( cn<N>::data ) .

26voto

Josh Matthews Points 4026

Je crois que MSVC et GCC supportent tous les deux une fonction __COUNTER__ jeton de préprocesseur qui se voit substituer une valeur monotone croissante à sa place.

3 votes

Vous devriez vérifier les types de beauté qui mènent à des mots comme duodecilliotonically si mes préfixes sont corrects... :P

11 votes

C'est la solution la plus courante, mais 1. elle n'est pas standard ; 2. elle n'est pas réutilisable - il n'y a qu'un seul compteur par unité de traduction ; 3. elle ne peut pas être lue sans être modifiée.

23voto

iammilind Points 29275

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

Le code de la bibliothèque suivante permet d'obtenir une fonctionnalité au niveau de l'espace de nom, c'est-à-dire que je réussis à mettre en œuvre counter_read y counter_inc ; mais pas le counter_inc_t (qui est incrémenté dans la fonction car template les classes ne sont pas autorisées dans les fonctions)

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 métaprogrammation de modèles et tire parti de la __LINE__ macro. Voir le résultat pour le code de votre réponse.

1 votes

Très bien ! Cependant, cela implique un niveau d'imbrication de modèles pour chaque ligne de source, donc pour les gros fichiers, cela ne compilera probablement pas.

1 votes

En outre, il sera confus s'il est utilisé dans deux fichiers d'en-tête différents. (Mais les espaces de noms peuvent être utilisés pour contenir les dégâts).

1 votes

1 << 9 est seulement 512 ;v) . Voir ideone.com/dOXTG . Comme vous pouvez le voir dans le message d'erreur, 512 est exactement la valeur la plus élevée qui est garantie pour fonctionner avec cette version de ce compilateur.

7voto

Matthieu M. Points 101624

Vous pourriez utiliser BOOST_PP_COUNTER de Boost.Preprocessor.

Avantage : il 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.

0 votes

Malheureusement aussi pour COMPTEUR Ce compteur ne présente pas les mêmes problèmes de support pour être utilisé dans un contexte global complet d'unités de traduction.

5voto

Rendaw Points 48

Voici une autre mise en œuvre alternative. https://stackoverflow.com/a/6174263/1190123 est probablement meilleure, mais même après avoir travaillé manuellement sur quelques incréments sur papier, je ne comprends toujours pas bien les calculs/filtrages.

Elle utilise la récursion de la fonction constexpr pour compter le nombre d'éléments non déclarés dans le modèle. Highest fonctions. __COUNTER__ est utilisé comme un mécanisme générationnel pour empêcher les nouvelles déclarations de Highest de faire de l'auto-récurrence.

Cela ne compile que sur clang pour moi (3.3). Je ne suis pas sûr qu'il soit conforme, mais j'ai bon espoir. g++ 4.8 échoue à cause d'une fonctionnalité non implémentée (selon l'erreur). Le compilateur Intel 13 échoue également, à cause d'un bogue constexpr.

Compteur de 256 niveaux

Le nombre maximum par compteur est de 250 (CounterLimit). CounterLimit peut être augmenté jusqu'à 256 sauf si vous implémentez le truc LCount ci-dessous.

Mise en œuvre

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

Essais

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

Sortie

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 les compteurs. J'ai fait 250 * 250 (bien que je n'ai pas vraiment testé le comptage au-delà de 2). CounterLimit doit être abaissé à environ 250 pour les limites de récursion du compilateur. Juste pour noter, cela a pris beaucoup plus de temps à compiler pour moi.

Mise en œuvre

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

Essais

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

0 votes

Notez que la limite s'applique au nombre de fois que le compteur peut être évalué, et non à sa valeur maximale. Désolé, j'aurais probablement dû expliquer les mathématiques que j'ai utilisées. Et en général, comment mon implémentation fonctionne c'est plutôt compliqué. Mais la mienne est O(log valeur limite) en lecture et écriture, alors que celle-ci semble être O(accès limite).

1 votes

Notez que vous pouvez utiliser __VA_ARGS__ et des macros variadiques pour passer , en tant qu'argument macro, évitant ainsi COMMA .

0 votes

Merci pour __VA_ARGS__ conseil ! Je ne voulais pas critiquer votre réponse ; même si vous l'avez expliquée, je ne suis pas sûr d'avoir les facultés mentales requises. Mais si tu ajoutais des explications, je les lirais avec attention.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X