web-dev-qa-db-fra.com

constexpr initialisation d'un membre statique à l'aide d'une fonction statique

Exigences

Je veux une valeur constexpr (c'est-à-dire une constante de temps de compilation) calculée à partir d'une fonction constexpr. Et je veux que ces deux soient étendus à l'espace de noms d'une classe, c'est-à-dire une méthode statique et un membre statique de la classe.

Premier essai

J'ai d'abord écrit ceci de la manière (pour moi) évidente:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

g++-4.5.3 -std=gnu++0x dit à cela:

error: ‘static int C1::foo(int)’ cannot appear in a constant-expression
error: a function call cannot appear in a constant-expression

g++-4.6.3 -std=gnu++0x Se plaint:

error: field initializer is not constant

Deuxième essai

OK, je pensais, peut-être que je devrais retirer les choses du corps de classe. J'ai donc essayé ce qui suit:

class C2 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));

g++-4.5.3 compilera cela sans se plaindre. Malheureusement, mon autre code utilise des boucles for basées sur une plage, donc je dois avoir au moins 4.6. Maintenant que je regarde de plus près la liste de support , il semble que constexpr nécessite également 4.6. Et avec g++-4.6.3 Je reçois

3:24: error: constexpr static data member ‘bar’ must have an initializer
5:19: error: redeclaration ‘C2::bar’ differs in ‘constexpr’
3:24: error: from previous declaration ‘C2::bar’
5:19: error: ‘C2::bar’ declared ‘constexpr’ outside its class
5:19: error: declaration of ‘const int C2::bar’ outside of class is not definition [-fpermissive]

Cela me semble vraiment étrange. En quoi les choses "diffèrent-elles dans constexpr" ici? Je n'ai pas envie d'ajouter -fpermissive car je préfère que mon autre code soit rigoureusement vérifié. Déplacer l'implémentation foo en dehors du corps de classe n'a eu aucun effet visible.

Réponses attendues

Quelqu'un peut-il expliquer ce qui se passe ici? Comment puis-je réaliser ce que j'essaie de faire? Je suis principalement intéressé par les réponses des types suivants:

  • Un moyen de faire fonctionner cela dans gcc-4.6
  • Une observation selon laquelle les versions ultérieures de gcc peuvent traiter correctement l'une des versions
  • Un pointeur vers la spécification selon laquelle au moins une de mes constructions devrait fonctionner, afin que je puisse bug les développeurs gcc sur le fait de le faire fonctionner
  • Information selon laquelle ce que je veux est impossible selon les spécifications, de préférence avec quelques informations sur la justification de cette restriction

D'autres réponses utiles sont également les bienvenues, mais ne seront peut-être pas acceptées aussi facilement.

31
MvG

La norme exige (section 9.4.2):

Un membre de données static de type littéral peut être déclaré dans la définition de classe avec le spécificateur constexpr; dans l'affirmative, sa déclaration doit spécifier un accolade-ou-égal-initialiseur dans lequel chaque clause d'initialisation qui est une expression-affectation est une expression constante.

Dans votre "deuxième tentative" et le code dans la réponse d'Ilya, la déclaration n'a pas d'initialisation d'accolade ou d'égalité .

Votre premier code est correct. Il est regrettable que gcc 4.6 ne l'accepte pas, et je ne sais pas où essayer commodément 4.7.x (par exemple, ideone.com est toujours bloqué sur gcc 4.5).

Ce n'est pas possible, car malheureusement, le Standard empêche d'initialiser un membre de données constexpr statique dans n'importe quel contexte où la classe est terminée. La règle spéciale pour les initialiseurs d'accolade ou d'égalité dans 9.2p2 s'applique uniquement aux membres de données non statiques, mais cela l'un est statique.

La raison la plus probable est que les variables constexpr doivent être disponibles en tant qu'expressions constantes de compilation à l'intérieur des corps des fonctions membres, de sorte que les initialiseurs de variables sont complètement définis avant les corps de fonction - ce qui signifie que la fonction est toujours incomplète (non définie) dans le contexte de l'initialiseur, puis cette règle entre en jeu, ce qui fait que l'expression n'est pas une expression constante:

une invocation d'une fonction constexpr non définie ou d'un constructeur constexpr non défini en dehors de la définition d'une fonction constexpr ou d'un constructeur constexpr;

Considérer:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};
19
Ben Voigt

1) L'exemple d'Ilya devrait être un code non valide basé sur le fait que la barre de membre de données constexpr statique est initialisée hors ligne, violant ainsi la déclaration suivante dans la norme:

9.4.2 [class.static.data] p3: ... Un membre de données statiques de type littéral peut être déclaré dans la définition de classe avec le spécificateur constexpr; dans l'affirmative, sa déclaration doit spécifier un initialiseur d'accolade ou égal dans lequel chaque clause d'initialisation qui est une expression d'affectation est une expression constante.

2) Le code dans la question de MvG:

class C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = foo(sizeof(int));
};

est valide pour autant que je le vois et intuitivement on s'attendrait à ce que cela fonctionne car le membre statique foo (int) est défini par le traitement temporel de la barre démarre (en supposant un traitement descendant). Quelques faits:

  • Je suis d'accord cependant que la classe C1 n'est pas complète au moment de l'invocation de foo (basée sur 9.2p2) mais complétude ou le caractère incomplet de la classe C1 ne dit pas si foo est défini en ce qui concerne la norme.
  • J'ai cherché dans la norme la définition des fonctions membres mais je n'ai rien trouvé.
  • Donc, la déclaration mentionnée par Ben ne s'applique pas ici si ma logique est valide:

    une invocation d'une fonction constexpr non définie ou d'un constructeur constexpr non défini en dehors de la définition d'une fonction constexpr ou d'un constructeur constexpr;

class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};

semble invalide mais pour des raisons différentes et pas simplement parce que foo est appelé dans l'initialiseur de bar . La logique est la suivante:

  • foo () est appelé dans l'initialiseur de la barre de membre constexpr statique , donc ça doit être une expression constante (par 9.4.2 p3).
  • puisqu'il s'agit d'une invocation d'une fonction constexpr, la substitution d'invocation de fonction (7.1.5 p5) entre en jeu.
  • Il n'y a aucun paramètre pour la fonction, donc ce qui reste est "la conversion implicite de l'expression retournée résultante ou de la liste d'initiation contreventée au type de retour de la fonction comme si par copie-initialisation." (7.1.5 p5)
  • l'expression de retour est juste bar , qui est une lvalue et la conversion de lvalue en rvalue est nécessaire.
  • mais par la puce 9 dans (5.19 p2) qui barre ne le fait pas satisfait car elle n'est pas encore initialisée:

    • une conversion de lvalue en rvalue (4.1), sauf si elle est appliquée à:
      • une valeur gl de type intégral ou énumération qui fait référence à un objet const non volatile avec une initialisation précédente, initialisé avec une expression constante.
  • par conséquent, la conversion lvalue-to-rvalue de bar ne donne pas une expression constante à défaut de l'exigence de (9.4.2 p3).

  • donc au point 4 de (5.19 p2), l'appel à foo () n'est pas une expression constante:

    une invocation d'une fonction constexpr avec des arguments qui, lorsqu'ils sont substitués par une substitution d'invocation de fonction (7.1.5), ne produisent pas une expression constante

5
user1574647
#include <iostream>

class C1 
{
public:
    constexpr static int foo(constexpr int x)
    { 
        return x + 1;
    }

    static constexpr int bar;
};

constexpr int C1::bar = C1::foo(sizeof(int));

int main()
{
    std::cout << C1::bar << std::endl;
    return 0;
}

Une telle initialisation fonctionne bien mais uniquement sur clang

3
Ilya Lavrenov

Probablement, le problème ici est lié à l'ordre des déclarations/définitions dans une classe. Comme vous le savez tous, vous pouvez utiliser n'importe quel membre avant même qu'il ne soit déclaré/défini dans une classe.

Lorsque vous définissez la valeur de constexpr dans la classe, le compilateur n'a pas la fonction constexpr disponible pour être utilisé car il est à l'intérieur de la classe.

Peut-être, Philip réponse, liée à cette idée, est un bon point pour comprendre la question.

Notez ce code qui se compile sans problème:

constexpr int fooext(int x) { return x + 1; }
struct C1 {
  constexpr static int foo(int x) { return x + 1; }
  constexpr static int bar = fooext(5);
};

constexpr static int barext = C1::foo(5);
2
EFenix