enum vs constexpr pour les constantes statiques réelles à l'intérieur des classes
permettez-moi de commencer par énoncer mon intention. Dans les vieux jours (C++), nous aurions le code comme:
class C
{
public:
enum {SOME_VALUE=27};
};
alors nous pourrions utiliser SOME_VALUE
tout au long de notre code comme une constante de temps de compilation et partout où le compilateur verrait C::SOME_VALUE
, il se contenterait d'insérer le littéral 27.
de nos jours, il semble plus acceptable de changer ce code en quelque chose comme:
class C
{
public:
static constexpr int SOME_VALUE=27;
};
cela semble beaucoup plus propre, donne SOME_VALUE
un type bien défini et semble être l'approche préférée à partir de C++11. Le problème (imprévu au moins pour moi) est que cela provoque également des scénarios où SOME_VALUE
doit être rendue externe. C'est, dans certains rpc fichier quelque part, nous devons ajouter:
constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage
les cas qui causent cela semblent être lorsque les références const à SOME_VALUE
sont utilisées, ce qui arrive très souvent dans le code de bibliothèque Standard C++ (Voir l'exemple au bas de cette question). Je j'utilise gcc 4.7.2 comme compilateur.
en raison de ce dilemme, je suis forcé de revenir à la définition de SOME_VALUE
en tant qu'enum (i.e., ancienne école) afin d'éviter d'avoir à ajouter une définition à un fichier RPC pour certaines, mais pas toutes mes variables statiques membres de constexpr. N'y a-t-il pas un moyen de dire au compilateur que constexpr int SOME_VALUE=27
signifie que SOME_VALUE
doit être traité seulement comme une constante de temps de compilation et jamais un objet avec liaison externe? Si vous voyez une référence de const utilisée avec, créez un temporaire. Si vous voyez son adresse prise, générez une erreur de temps de compilation si c'est nécessaire, parce que c'est une constante de temps de compilation et rien de plus.
voici un code échantillon apparemment Bénin qui nous oblige à ajouter la définition de SOME_VALUE
dans un fichier cpp (encore une fois, testé avec gcc 4.7.2):
#include <vector>
class C
{
public:
static constexpr int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE); // Will cause an undefined reference error
// at link time, because the compiler isn't smart
// enough to treat C::SOME_VALUE as the literal 5
// even though it's obvious at compile time
}
ajouter la ligne suivante au code au fichier scope résoudra l'erreur:
constexpr int C::SOME_VALUE;
6 réponses
pour rappel, la version static constexpr
fonctionnera comme vous l'aviez prévu en C++17. De N4618 Annexe D. 1 [depr.static_constexpr] :
D. 1 Redeclaration de
static constexpr
les membres de données [depr.static_constexpr]pour la compatibilité avec les normes internationales C++ antérieures, un
constexpr
membre de données statiques peut être redondant non déclaré en dehors de la classe sans initialiseur. Cette utilisation est déconseillé. [ exemple:
struct A {
static constexpr int n = 5; // definition (declaration in C++ 2014)
};
constexpr int A::n; // redundant declaration (definition in C++ 2014)
- fin exemple ]
le texte de norme pertinent qui permet cela est N4618 9.2.3 [classe.statique.données] / 3 :
[...] Un élément de données statiques en ligne peut être défini dans la définition de la classe et peut spécifier un initialiseur . Si le membre est déclaré avec le spécificateur
constexpr
, il peut être non déclaré dans l'espace de noms scope sans initialiseur (cet usage est déprécié; voir D. 1). [...]
cela vient avec la même machine qui a introduit la non - constexpr
version de la même chose, membres de données statiques en ligne .
struct A {
static inline int n = 5; // definition (illegal in C++ 2014)
};
inline int A::n; // illegal
vous avez trois options ici:
-
si votre classe est un modèle, alors mettez la définition de membre statique dans l'en-tête lui-même. Compilateur est nécessaire pour l'identifier comme une seule définition parmi plusieurs unités de traduction (voir [basic.def.odr] / 5)
-
si votre classe n'est pas un modèle, vous pouvez facilement la mettre dans le fichier source
-
alternativement déclarer constexpr fonction membre statique getSomeValue():
class C { public: static constexpr int getSomeValue() { return 27; } };
j'irais avec la classe enum:
http://en.cppreference.com/w/cpp/language/enum
http://www.stroustrup.com/C++11FAQ.html#enum
à Partir du premier lien:
enum class Color { RED, GREEN=20, BLUE};
Color r = Color::BLUE;
switch(r) {
case Color::RED : std::cout << "red\n"; break;
case Color::GREEN : std::cout << "green\n"; break;
case Color::BLUE : std::cout << "blue\n"; break;
}
// int n = r; // error: no scoped enum to int conversion
int n = static_cast<int>(r); // OK, n = 21
de nos jours, la voie préférée est:
enum class : int C { SOME_VALUE = 5 };
de la norme c++ N3797 S3.5/2-3
un nom est considéré comme ayant un lien lorsqu'il peut dénoter le même objet, référence, Fonction, type, gabarit, namespace ou valeur qu'un nom introduit par une déclaration dans une autre portée:
- Lorsqu'un nom a un lien externe , l'entité qu'il désigne peut être désignée par des noms provenant d'autres unités de traduction ou d'autres unités de la même unité de traduction.
- Lorsqu'un nom a un lien interne , l'entité qu'il dénote peut être désignée par des noms d'autres domaines dans la même unité de traduction.
- Lorsqu'un nom n'a pas de lien , l'entité qu'il désigne ne peut pas être désignée par des noms d'autres domaines.
un nom ayant une portée d'espace de noms (3.3.6) a un lien interne s'il s'agit du nom de
- une variable, une fonction ou un modèle de fonction qui est explicitement déclaré statique; ou
- une variable non volatile qui est explicitement déclarée const ou constexpr et qui n'est ni déclarée extern ni déclarée antérieurement comme ayant une liaison externe; ou
- une donnée membre d'un syndicat anonyme.
ma lecture est que dans le code suivant:
public:
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
};
static constexpr int SOME_VALUE=5;
constexpr int SOME_VALUE=5;
les 4 cas de SOME_VALUE
ont un lien interne. Ils devraient être en lien avec une référence à SOME_VALUE
dans la même unité de traduction et ne pas être visible ailleurs.
Évidemment, la première est une déclaration et non une définition. Il a besoin d'une définition au sein de la même unité de traduction. Si GCC le dit et MSVC ne le fait pas, alors MSVC a tort.
pour remplacer un enum, le numéro 2 devrait fonctionner. Il y a toujours un lien interne sans le mot clé static
.
[révisé en réponse au commentaire]
vous pouvez le faire
class C
{
public:
static const int SOME_VALUE=5;
};
int main()
{
std::vector<int> iv;
iv.push_back(C::SOME_VALUE);
}
ce n'est même pas C++11, juste c++98