2017-09-18 1 views
16

Bien que j'ai code utilisé comme ça avant, et il est clair que le compilateur a suffisamment d'informations pour travailler, je ne comprends pas vraiment pourquoi cette compile:Constante intégrale transmise par valeur, traitée comme constexpr?

template <class T, class I> 
auto foo(const T& t, I i) { 
    return std::get<i>(t); 
} 

int main() 
{ 
    std::cerr << foo(std::make_tuple(3,4), std::integral_constant<std::size_t, 0>{}); 
    return 0; 
} 

exemple en direct: http://coliru.stacked-crooked.com/a/fc9cc6b954912bc5.

Semble fonctionner à la fois avec gcc et clang. Le fait est quea une conversion constexpr à l'entier stocké, constexpr les fonctions membres prennent implicitement l'objet lui-même comme argument, et par conséquent une telle fonction ne peut pas être utilisée dans un contexte constexpr à moins que l'objet que nous appelons le membre fonctionne lui-même peut être traité comme constexpr.

Ici, i est un argument passé à foo, et donc i ne peut certainement pas être traité comme. Pourtant, c'est. Un exemple encore plus simple:

template <class I> 
void foo(I i) { 
    constexpr std::size_t j = i; 
} 

Cette compile aussi, tant que std::integral_constant<std::size_t, 0>{} est passé à foo. J'ai l'impression qu'il me manque quelque chose d'évident sur les règles constexpr. Existe-t-il une exception pour les types sans état, ou autre chose? (ou, peut-être, un bug de compilateur dans deux compilateurs majeurs? Ce code semble fonctionner sur clang 5 et gcc 7.2). Edit: une réponse a été postée, mais je ne pense pas que ce soit suffisant. En particulier, compte tenu de la dernière définition de foo, pourquoi:

foo(std::integral_constant<std::size_t, 0>{}); 

Compile, mais pas:

foo(0); 

Les deux 0 et std::integral_constant<std::size_t, 0>{} sont des expressions constantes.

Édition 2: Il semble que cela se résume au fait que l'appel d'une fonction membre constexpr même sur un objet qui n'est pas une expression constante, peut lui-même être considéré comme une expression constante, tant que this est inutilisé. Ceci est pris comme évident. Je ne considère pas cette évidence:

constexpr int foo(int x, int y) { return x; } 

constexpr void bar(int y) { constexpr auto x = foo(0, y); } 

Cela ne compile pas, parce que y passé dans foo n'est pas une expression constante. C'est être inutilisé n'a pas d'importance. Par conséquent, une réponse complète doit afficher une sorte de langage de la norme pour justifier le fait qu'une fonction membre constexpr peut être utilisée comme une expression constante, même sur un objet d'expression non constante, tant que this est inutilisé.

+0

Fonctionne aussi sur Visual C++: https://godbolt.org/g/Cknxco – Justin

+0

Semble échouer sur le compilateur intel: https://godbolt.org/g/wLc5dw – Justin

+2

@Justin Bon catch bien que vu que c'est icc Je parie que c'est un autre bug sans rapport :-). –

Répondre

17

Les règles pour les expressions de constante de temps de compilation ont changé avec constexpr, beaucoup, mais elles ne sont pas nouvelles. Avant constexpr, il y avait déjà des expressions de constante de temps de compilation ... et les anciennes règles sont conservées comme des cas spéciaux dans la nouvelle spécification, pour éviter de casser d'énormes quantités de code existant.

Pour l'essentiel, les anciennes règles traitaient des constantes de type intégrale appelées expression de constante intégrale ... ce qui correspond exactement à la situation à laquelle vous faites face. Donc non, il n'y a rien de bizarre dans les règles constexpr ...ce sont les autres règles plus anciennes qui n'ont rien à voir avec constexpr.

Un e conditionnel expression est une expression constante de base à moins que l'évaluation des e, suivant les règles de la machine abstraite, évaluerait l'une des expressions suivantes:

...

  • une conversion lvalue-à-valeur à moins qu'elle ne soit appliquée à
    • un glvalue non volatile de type intégrale ou énumération qui fait référence à un objet const non-volatile complet avec une précé initialisation ding, initialisé avec une expression constante, ou
    • un glvalue non-volatile qui fait référence à un sous-objet d'une chaîne de caractères, ou
    • un glvalue non volatile qui se réfère à un objet non-volatile défini avec constexpr, ou cela fait référence à un sous-objet non mutable d'un tel objet, ou
    • un glvalue non volatil de type littéral qui fait référence à un objet non volatil dont la durée de vie a commencé dans l'évaluation de e;

Vous avez raison que le troisième subbullet ne s'applique pas. Mais le premier fait. Donc vous avez une interaction intéressante entre les nouvelles règles qui permettent aux retours de fonctions d'être constantes en temps de compilation, en fonction des règles d'évaluation de la machine abstraite, et le comportement hérité permettant de compiler des valeurs entières même si non marqué comme tel.


Voici un exemple rapide pourquoi il n'a pas d'importance que this est un argument implicite. Être un argument ne signifie pas un objet est évalué:

constexpr int blorg(bool const flag, int const& input) 
{ 
    return flag? 42: input; 
} 

int i = 5; // 5 is an integral constant expression, but `i` is not 
constexpr int x = blorg(true, i); // ok, `i` was an argument but never evaluated 
constexpr int y = blorg(false, i); // no way 

Pour std::integral_constant fonctions membres, vous pouvez considérer comme *thisi dans la fonction blorg - si l'exécution ne déréférencer, il est ok pour qu'il soit passé sans être une constante à la compilation.

+0

J'ai pensé à cette réponse un peu, pour moi, il semble que si cette réponse est correcte, mon raisonnement serait que 'foo (0)' dans ma question mise à jour devrait également compiler, mais ce n'est pas le cas. Je pense que comprendre exactement la distinction entre les deux appels dans mon édition répondra à la question clairement. –

+0

Je pense que rien de cela ne s'applique car il n'y a pas de conversion lvalue-à-valeur. – cpplearner

+0

@NirFriedman: Dans les cas qui fonctionnent, la seule chose évaluée est un paramètre de modèle intégral, qui tombe dans la sous-puce 1. Dans votre nouveau code de rupture, un paramètre variable d'exécution est évalué. L'appel d'une fonction membre non virtuelle n'évalue pas nécessairement l'expression sur laquelle elle est invoquée ... Les opérateurs de conversion 'std :: integral_constant' n'accèdent à aucun membre de données non statiques de' this', et par conséquent ne le font pas. devenir illégal dans un contexte constant. –

12

La raison pour laquelle cela fonctionne:

template <class T, class I> 
auto foo(const T& t, I i) { 
    return std::get<i>(t); 
} 

est parce que aucune des raisons pour lesquelles il échouerait applique. Lorsque i est un std::integral_constant<size_t, S>, i peut être utilisé comme expression constante convertie de type size_t car cette expression passe par constexpr operator size_t(), ce qui renvoie simplement un paramètre de modèle (prvalue) en tant que valeur. C'est une expression constante parfaitement valide. Notez que this n'est pas référencé - juste parce que c'est une fonction membre ne viole pas, en soi, la contrainte d'expression constante.

Fondamentalement, il n'y a rien "runtimey" sur i ici.

D'un autre côté, si i étaient un int (par voie de foo(0)), puis en appelant std::get<i> impliquerait une conversion lvalue à rvalue pour i, mais ce cas ne répondrait pas aux any of the criteria depuis i ne pas l'initialisation précédente avec une expression constante, ce n'est pas un littéral de chaîne, il n'a pas été défini avec constexpr, et il n'a pas commencé sa durée de vie dans cette expression.

+0

À droite, l'important est que la valeur soit extraite du paramètre template, pas de l'argument function. –

+0

[Un paramètre de modèle non référence de type non-référence est une prvalue.] (Http://eel.is/c++draft/temp.param#6.sentence-1) – cpplearner

+0

@cpplearner Oh oui, oubliez toujours que . Bravo, corrigé – Barry