Question constexpr et l'initialisation d'un pointeur de const statique avec reinterpret cast, quel compilateur a raison?


Considérons le morceau de code suivant:

struct foo {
  static constexpr const void* ptr = reinterpret_cast<const void*>(0x1);
};

auto main() -> int {
  return 0;
}

L’exemple ci-dessus compile bien dans g ++ v4.9 (Démo en direct), alors qu’il ne parvient pas à compiler en clang v3.4 (Démo en direct) et génère l'erreur suivante:

erreur: la variable constexpr 'ptr' doit être initialisée par une expression constante

Des questions:

  • Lequel des deux compilateurs a raison selon la norme?

  • Quelle est la manière appropriée de déclarer une telle expression?


14
2018-06-24 23:48


origine


Réponses:


TL; DR

clang est correct, cela est connu gcc punaise. Vous pouvez soit utiliser intptr_t au lieu de cela et lancer lorsque vous devez utiliser la valeur ou si cela ne fonctionne pas, alors les deux gcc et clang supportez un petit travail documenté qui devrait permettre votre cas d'utilisation particulier.

Détails

Alors clang est correct sur celui-ci si nous allons à la projet de norme C ++ 11 section 5.19  Expressions constantes paragraphe 2 dit:

Une expression conditionnelle est une expression constante de base à moins   implique l'un des suivants en tant que sous-expression potentiellement évaluée   [...]

et comprend la puce suivante:

- un reinterpret_cast (5.2.10);

Une solution simple serait d'utiliser intptr_t:

static constexpr intptr_t ptr = 0x1;

et ensuite lancer plus tard quand vous avez besoin de l'utiliser:

reinterpret_cast<void*>(foo::ptr) ;

Il peut être tentant de s'en tenir à cela, mais cette histoire devient plus intéressante cependant. Ceci est connu et toujours ouvert gcc bug voir Bogue 49171: [C ++ 0x] [constexpr] Les expressions constantes prennent en charge reinterpret_cast. Il ressort clairement de la discussion que gcc les devs ont des cas d'utilisation clairs pour ceci:

Je crois que j'ai trouvé une utilisation conforme de reinterpret_cast en constante   expressions utilisables en C ++ 03:

//---------------- struct X {  X* operator&(); };

X x[2];

const bool p = (reinterpret_cast<X*>(&reinterpret_cast<char&>(x[1]))
- reinterpret_cast<X*>(&reinterpret_cast<char&>(x[0]))) == sizeof(X);

enum E { e = p }; // e should have a value equal to 1
//----------------

Fondamentalement, ce programme montre la technique, la bibliothèque C ++ 11   La fonction addressof est basée sur et exclut donc reinterpret_cast    sans condition des expressions constantes dans le langage de base rendrait ce programme utile invalide et le rendrait impossible à   déclarer addressof comme une fonction constexpr.

mais n'ont pas pu obtenir une exception sculptée pour ces cas d'utilisation, voir questions closes 1384:

Bien que reinterpret_cast était autorisé dans la constante d'adresse   expressions en C ++ 03, cette restriction a été implémentée dans certains   compilateurs et n’a pas prouvé qu’il y avait des quantités de code importantes. CWG   estimé que les complications de traiter avec des pointeurs dont les formes   changé (l'arithmétique du pointeur et la déréférence ne peuvent pas être autorisées sur   de tels indicateurs) l'emportaient sur l'utilité possible de la relaxation du courant   restriction.

MAIS Apparemment gcc et clang supporte une petite extension documentée qui permet de replier constamment des expressions non constantes en utilisant __builtin_constant_p (exp) et donc les expressions suivantes sont acceptées par les deux gcc et clang:

static constexpr const void* ptr = 
  __builtin_constant_p( reinterpret_cast<const void*>(0x1) ) ? 
    reinterpret_cast<const void*>(0x1) : reinterpret_cast<const void*>(0x1)  ;

Trouver la documentation pour cela est presque impossible, mais cette llvm commit est informatif avec les extraits suivants fournissent des lectures intéressantes:

  • soutenir le gcc __builtin_constant_p ()? ...: ... hack pliant en C ++ 11

et:

+ // __builtin_constant_p? : est magique, et est toujours une constante potentielle.

et:

  • // Cette macro force son argument à être plié de façon constante, même si ce n'est pas le cas
  • // sinon une expression constante.
  • définir fold (x) (__builtin_constant_p (x)? (x): (x))

Nous pouvons trouver une explication plus formelle de cette fonctionnalité dans l’e-mail gcc-patches: C expressions constantes, corrections VLA, etc. qui dit:

De plus, les règles pour __builtin_constant_p appellent comme conditionnel   condition d'expression dans la mise en œuvre sont plus détendus que ceux   dans le modèle formel: la moitié sélectionnée de l'expression conditionnelle   est entièrement plié indépendamment de savoir si elle est formellement une constante   expression, puisque __builtin_constant_p teste un argument complètement plié   lui-même


12
2018-06-25 04:05



Clang a raison. Le résultat d'une réinterprétation-cast n'est jamais une expression constante (cf. C ++ 11 5.19 / 2).

Le but des expressions constantes est de pouvoir les raisonner en tant que valeurs et de valider les valeurs. Ce que vous écrivez n'est pas nécessairement un pointeur valide (car ce n'est pas l'adresse d'un objet, ou lié à l'adresse d'un objet par l'arithmétique du pointeur), vous n'êtes donc pas autorisé à l'utiliser comme une expression constante. Si vous voulez juste stocker le numéro 1, stockez-le comme uintptr_t et faire la réinterprétation de la distribution sur le site d'utilisation.


En passant, pour élaborer un peu sur la notion de "pointeurs valables", considérez ce qui suit: constexpr pointeurs:

int const a[10] = { 1 };
constexpr int * p1 = a + 5;


constexpr int b[10] = { 2 };
constexpr int const * p2 = b + 10;

// constexpr int const * p3 = b + 11;    // Error, not a constant expression

// static_assert(*p1 == 0, "")           // Error, not a constant expression

static_assert(p2[-2] == 0, "");          // OK

// static_assert(p2[1] == 0, "");        // Error, "p2[2] would have UB"

static_assert(p2 != nullptr, "");        // OK

// static_assert(p2 + 1 != nullptr, ""); // Error, "p2 + 1 would have UB"

Tous les deux p1 et p2 sont des expressions constantes. Mais si le résultat de l'arithmétique d'un pointeur est une expression constante, cela dépend si ce n'est pas UB! Ce type de raisonnement serait essentiellement impossible si vous autorisiez les valeurs de reinterpret_casts à être des expressions constantes.


11
2018-06-24 23:56