web-dev-qa-db-fra.com

enum vs constexpr pour les constantes statiques réelles à l'intérieur des classes

Permettez-moi de commencer par exprimer mon intention. Dans l'ancien temps (C++), nous aurions du code comme:

class C
{
public:
  enum {SOME_VALUE=27};
};

Ensuite, nous pourrions utiliser SOME_VALUE dans tout notre code comme constante de temps de compilation et partout où le compilateur verrait C::SOME_VALUE, il suffit 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 rendu externe. Autrement dit, dans un fichier cpp quelque part, nous devons ajouter:

constexpr int C::SOME_VALUE; // Now C::SOME_VALUE has external linkage

Les cas qui provoquent cela semblent être lorsque const fait référence à SOME_VALUE sont utilisés, ce qui arrive assez souvent dans le code de la bibliothèque standard C++ (voir l'exemple au bas de cette question). J'utilise gcc 4.7.2 comme compilateur d'ailleurs.

En raison de ce dilemme, je suis obligé de revenir à la définition de SOME_VALUE en tant qu'énumération (c'est-à-dire old school) afin d'éviter d'avoir à ajouter une définition à un fichier cpp pour certaines, mais pas toutes mes variables membres constexpr statiques. 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é niquement comme une constante de temps de compilation et jamais comme un objet avec une liaison externe? Si vous voyez une référence 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 ce qui est nécessaire, car c'est une constante de temps de compilation et rien de plus.

Voici un exemple de code 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
}

L'ajout de la ligne suivante au code à la portée du fichier résoudra l'erreur:

constexpr int C::SOME_VALUE;
65

Pour mémoire, le static constexpr la version fonctionnera comme vous l'aviez prévu en C++ 17. De N4618 Annexe D.1 [depr.static_constexpr] :

D.1 Redéclaration de static constexpr membres de données [depr.static_constexpr]

Pour la compatibilité avec les normes internationales C++ antérieures, un membre de données statiques constexpr peut être redéclaré de manière redondante en dehors de la classe sans initialiseur. Cette utilisation est déconseillée. [ 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 standard pertinent qui le permet est N4618 9.2.3 [class.static.data]/ :

[...] Un membre de données statiques en ligne peut être défini dans la définition de classe et peut spécifier un accolade-ou-égal-initialiseur . Si le membre est déclaré avec le spécificateur constexpr, il peut être redéclaré dans la portée de l'espace de noms sans initialiseur (cette utilisation est déconseillée; voir D.1). [...]

Cela vient avec la même machinerie qui a introduit la version non -constexpr 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
8
TBBle

Vous avez trois options ici:

  1. Si votre classe est un modèle, mettez la définition de membre statique dans l'en-tête lui-même. Le compilateur doit l'identifier comme une seule définition sur plusieurs unités de traduction (voir [basic.def.odr]/5)

  2. Si votre classe n'est pas un modèle, vous pouvez facilement le mettre dans le fichier source

  3. Vous pouvez également déclarer la fonction membre statique constexpr getSomeValue ():

    class C
    {
    public:
        static constexpr int getSomeValue() { return 27; }
    };
    
9
apoorvkul

J'irais avec la classe enum:

http://en.cppreference.com/w/cpp/language/enum

http://www.stroustrup.com/C++11FAQ.html#enum

Depuis le 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
2

De la norme C++ N3797 S3.5/2-3

Un nom est censé avoir un lien lorsqu'il peut désigner le même objet, référence, fonction, type, modèle, espace de noms 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 de portées d'autres unités de traduction ou d'autres portées de la même unité de traduction.

- Lorsqu'un nom a un lien interne, l'entité qu'il désigne peut être désignée par des noms provenant d'autres étendues 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 référencée par des noms d'autres étendues.

Un nom ayant une portée d'espace de noms (3.3.6) a une liaison 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 ni explicitement déclarée extern ni précédemment déclarée comme ayant une liaison externe; ou

- un membre de données d'un syndicat anonyme.

Ma lecture est celle du 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 instances de SOME_VALUE ont un lien interne. Ils doivent être liés à une référence à SOME_VALUE dans la même unité de traduction et non visible ailleurs.

De toute évidence, 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 que MSVC ne le fait pas, alors MSVC a tort.

Pour remplacer une énumération, le numéro 2 devrait fonctionner correctement. Il a toujours un lien interne sans le mot clé static.

[Modifié en réponse au commentaire]

1
david.pfx

De nos jours, le moyen préféré est:

enum class : int C { SOME_VALUE = 5 };
1
edwinc