À 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"
}
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';
}
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
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.
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.
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.
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;
}
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
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.
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.
#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
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.
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);
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.
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 .