web-dev-qa-db-fra.com

La spécification de constexpr sur le constructeur rend-elle automatiquement tous les objets créés à partir de lui constexpr?

Voici mon code:

class test{
    public:
    constexpr test(){

    }

    constexpr int operator+(const test& rhs){
        return 1;
    }
};



int main(){

    test t;                         //constexpr Word isn't necessary
    constexpr int b = t+test();     // works at compile time!


    int w = 10;                     // ERROR constexpr required
    constexpr int c = w + 2;        // Requires w to be constexpr
    return 0;
}

Je remarque que cela a fonctionné même si je n'ai pas spécifié que test devait être constexpr. J'ai essayé de répliquer le résultat en faisant de même avec int mais j'obtiens des erreurs. Plus précisément, il veut que mon int w À l'intérieur du constexpr int c = w + 2; Soit constexpr. De ma première tentative qui utilise test, Cela a-t-il fonctionné parce que j'ai déjà utilisé constexpr sur le constructeur? Si tel est le cas, serait-il bon de supposer que toutes les classes qui ont constexpr sur leurs constructeurs auront pour résultat que tous les objets instanciés ou créés avec lui seront constexpr?

Question bonus:

Si j'ai un constructeur constexpr, est-ce mauvais de faire quelque chose comme ça? test * t = new test(); ?

35

Avoir un constructeur constexpr ne fait pas de déclarations de cette variable automatiquement constexpr, donc t n'est pas un constexpr. Ce qui se passe dans ce cas, c'est que vous appelez une fonction constexpr, cette ligne:

constexpr int b = t+test(); 

peut être visualisé comme suit:

constexpr int b = t.operator+( test() ); 

La question est donc de savoir si test() est une expression constante, ce qu'elle est puisque le constructeur est constexpr et ne tombe sous aucune des exceptions du projet de section standard C++ 11 5.19 [expr.const] paragraphe 2 qui dit:

Une expression conditionnelle est une expression constante de base, sauf si elle implique l'un des éléments suivants comme une sous-expression potentiellement évaluée [...]

et comprend la puce suivante:

  • une invocation d'une fonction autre qu'un constructeur constexpr pour une classe littérale ou une fonction constexpr [Remarque: la résolution de surcharge (13.3) est appliquée comme d'habitude - note de fin];

[...]

  • une invocation d'un constructeur constexpr avec des arguments qui, lorsqu'ils sont substitués par une substitution d'invocation de fonction (7.1.5), ne produisent pas toutes les expressions constantes pour les appels de constructeur et les expressions complètes dans les initialiseurs mem

  • une invocation d'une fonction constexpr ou d'un constructeur constexpr qui dépasserait les limites de récursivité définies par l'implémentation (voir l'annexe B);

Nous pouvons le voir plus facilement en apportant quelques petites modifications à test en introduisant une variable membre x:

class test{
    public:
    constexpr test(){

    }

    constexpr int operator+(const test& rhs) const {
        return x + 1  ;
    }

    int x = 10 ;
};

Tenter d'y accéder dans operator + Et nous pouvons voir que la ligne suivante échoue maintenant:

constexpr int b = t+test();

avec l'erreur suivante de clang (voir en direct):

error: constexpr variable 'b' must be initialized by a constant expression
constexpr int b = t+test();     // works at compile time!
              ^   ~~~~~~~~

note: read of non-constexpr variable 't' is not allowed in a constant expression
    return x + 1  ;
           ^

Il échoue car t n'est pas une variable constexpr et donc ses sous-objets ne sont pas non plus des variables constexpr.

Votre deuxième exemple:

 constexpr int c = w + 2;  

ne fonctionne pas car il relève de l'une des exceptions du projet de section standard C++ 11 5.19 [expr.const] :

  • une conversion lvalue-to-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, ou
19
Shafik Yaghmour

L'effet qu'un constructeur constexpr a sur le type de classe peut être lu dans la norme C++

3.9 Types

(...)

  1. Un type est un type littéral s'il est:

    • c'est un type d'agrégat (8.5.1) ou a au moins un constructeur constexpr ou un modèle de constructeur qui n'est pas un constructeur de copie ou de déplacement

(...)

Ainsi, les constructeurs constexpr signifient que l'initialisation statique peut être effectuée et utilise comme this un sont possibles:

#include <iostream>

struct test {
    int val; 
    constexpr test(int val) : val(val) { }
};

template<int N>
struct CC {
    double m[N]; 
};

int main()
{
    CC<test(6).val> k; // usage where compile time constant is required
    std::cout << std::end(k.m) - std::begin(k.m) << std::endl; 
    return 0;
}

Le simple fait que test soit une classe littérale ne signifie pas que toutes ses instances seront des expressions constantes :

#include <iostream>

struct test {
    int val;
    constexpr test(int val) : val(val) { }
};

int main()
{
    test a(1); 
    ++a.val; 
    std::cout << a.val << std::endl;
    return 0;
}

Demo

Dans l'exemple ci-dessus, l'instance a n'a pas été déclarée comme constante, même si a pourrait être une constante constexpr, elle n'en est pas une (elle peut donc être modifiée).

10
Nikos Athanasiou

La clé constexpr Word de mes expériences dans cette réponse indique plus ou moins au compilateur qu'il doit être capable de résoudre statiquement tous les chemins de code donnés dans cet appel. Autrement dit, au moins en ce moment (il semblerait), tout doit être déclaré constexpr le long de ce chemin de code sinon il échouera. Par exemple, dans votre code, l'affectation initiale de constexpr à b échouera si vous ne déclarez pas l'opérateur ou le constructeur constexpr. Il semble que le constexpr ne prend effet que lorsque vous attribuez à une variable qui est déclarée constexpr, sinon il ne semble que servir de conseiller au compilateur pour que le chemin de code puisse être optimisé via une évaluation statique, mais il n'est pas garanti de le faire si vous ne l'instruisez pas explicitement avec une affectation de variable constexpr.

Cela étant dit, il semblerait que la déclaration d'un constructeur constexpr n'a aucun effet dans des circonstances normales. Le code machine ci-dessous a été produit avec la ligne de commande suivante:

g++ -std=c++11 -Wall -g  -c main.cpp -o obj/Debug/main.o
g++  -o bin/Debug/TestProject obj/Debug/main.o  

Et donc votre affectation b produit ce code:

0x4005bd    Push   rbp
0x4005be    mov    rbp,rsp
0x4005c1    mov    DWORD PTR [rbp-0x4],0x1
0x4005c8    mov    eax,0x0
0x4005cd    pop    rbp
0x4005ce    ret

Cependant, si vous supprimez la déclaration constexpr sur la variable b:

0x4005bd    Push   rbp
0x4005be    mov    rbp,rsp
0x4005c1    sub    rsp,0x10
0x4005c5    lea    rax,[rbp-0x5]
0x4005c9    mov    rdi,rax
0x4005cc    call   0x4005ee <test::test()>
0x4005d1    lea    rdx,[rbp-0x5]
0x4005d5    lea    rax,[rbp-0x6]
0x4005d9    mov    rsi,rdx
0x4005dc    mov    rdi,rax
0x4005df    call   0x4005f8 <test::operator+(test const&) const>
0x4005e4    mov    DWORD PTR [rbp-0x4],eax
0x4005e7    mov    eax,0x0
0x4005ec    leave
0x4005ed    ret

Il semble être géré comme si l'opérateur et le constructeur n'étaient pas déclarés constexpr, mais c'est une situation où vous devriez vraiment consulter les détails de votre compilateur.

3
Ragora