1
votes

Les fonctions membres sont-elles disponibles pendant la compilation en C ++?

La tentative de compilation des éléments suivants à l'aide des normes c ++ 11 (en utilisant constexpr) s'est terminée avec succès:

#include <iostream>

class test{
 public:
 int getId(){
   constexpr int id = 5;
   return id;
 }
};

int main(){
 test t;
 std::cout << t.getId() << std::endl;
 return 0;
}

Pendant la compilation, test n'existe pas encore, cependant, le code ci-dessus se compile très bien. Si test n'existe pas encore, comment getId peut-il exister pendant la compilation?

Exemple de travail complet:

class test{
 public:
 int getId(){
   constexpr int id = 5;
   return id;
 }
};


12 commentaires

Dans le code ci-dessus, getId n'a pas du tout besoin d'être compilé - c'est du code inaccessible. Il sera cependant analysé, bien sûr. À quel genre d'existence faites-vous référence avec "comment getId peut-il exister pendant la compilation?"?


Eh bien, puisque je n'avais pas à ajouter static à mon constexpr, j'ai pensé que la fonction pourrait être disponible pendant la compilation. alors ce n'est pas le cas?


Pourquoi une fonction inline serait-elle acceptable ici? J'essaie toujours d'évaluer id pendant la compilation où la classe elle-même n'existe pas encore.


Veuillez expliquer ce que vous entendez par «disponible au moment de la compilation». La fonction elle-même n'est pas constexpr , juste une variable dans la fonction.


constexpr signifie "évaluer pendant la compilation", n'est-ce pas? si je veux évaluer id pendant la compilation, à quoi appartient aussi id ? il n'est pas statique et doit donc appartenir à sa fonction parente getId qui appartient à sa classe parente test . id n'existe pas tout seul, il n'existe pas tant que la classe n'est pas initialisée, ce qui initialisera les fonctions? droit? c'est là que ma confusion est. Si getId n'existe pas avant l'exécution, comment une variable sous-jacente peut-elle exister pendant la compilation?


@Josh Non, constexpr signifie "ceci peut être évalué pendant la compilation". Les éléments marqués constexpr peuvent également être évalués lors de l'exécution.


Ah donc dans mon exemple complet ci-dessus, constexpr est simplement ignoré?


Non, la constexpr n'est pas ignorée. id est évalué au moment de la compilation (puisque c'est la seule partie que vous vouliez faire constexpr, qu'espérez-vous qu'il se passe?). getId et 'test t' ne sont pas constexpr, donc ils n'ignorent pas constexpr, ils n'ont jamais été constexpr au départ.


Donc, dans votre exemple, getId est une méthode d'exécution (et non une heure de compilation). Dans le cas des méthodes en ligne, le compilateur peut être en mesure de voir à travers cela et d'évaluer le lot complet au moment de la compilation, mais ce n'est pas garanti.


Je m'attends à ce que le compilateur lance une erreur et ne compile même pas. Que signifie le fait de dire que id est évalué au moment de la compilation ? comment peut-il évaluer id pendant la compilation si getId (le parent) n'est évalué que pendant l'exécution.


«5» n'a pas besoin d'être évalué. Si vous changez id en: "constexpr int id = 5 + 5 * 6 - 22/11;" Le constexpr serait toujours évalué au moment de la compilation. Il se trouve que «5» n'est pas exactement une expression intéressante.


En relation: Différence entre constexpr et const < / a>. Cela contient des informations utiles, mais cela pourrait ne pas être un vrai doublon.


3 Réponses :


0
votes

Vous pouvez utiliser la fonction membre comme constexpr, mais vous devez la déclarer comme constexpr.

https://godbolt.org/z/P2fcfK

main:
        mov     eax, 110
        ret

/ edit C'est le code que vous avez posté ....

#include <iostream>

struct Dave 
{
  const int a;

  constexpr Dave(const int b) : a(b) {}

  constexpr int var() const {
    return 5 * a;
  }
};

int main()
{
    constexpr int c = Dave(22).var();
    return c;
}

\ edit2 C'est l'expression la plus simple possible qui peut être évaluée au moment de la compilation:

constexpr int var(const int a) {
  return 5 * a;
}

constexpr int foo = var(22); //< will be evaluated at compile time. 

C'est une expression légèrement plus intéressante

constexpr int var(const int a) {
  constexpr int id = 5 * a; //< invalid! a is not constexpr!
  return id;
}

Dans ce cas, l'expression peut être évaluée au moment de la compilation. Voici un exemple de quelque chose qui ne peut être évalué au moment de la compilation:

int var(int a) {
  constexpr int id = 5 * a; //< invalid! a is not constexpr!
  return id;
}

getId () ne peut pas être constexpr ici, car nous devons le déclarer comme constexpr, cependant une approche naïve échouera:

constexpr int id = 5 * 5 + 22 / 11 - 9 * 65;

Elle échoue car nous utilisons la variable 'a' dans une seconde expression. Cependant, cela fonctionnera (nous avons supprimé la deuxième expression et l'avons repliée en une seule expression)

constexpr int id = 5;

Nous pouvons maintenant le faire avec des variables membres et un ctor, mais nous devons nous assurer que le ctor est constexpr. Par exemple:

#include <iostream>

class test{
 public:
 int getId(){ //< this method is NOT constexpr
   constexpr int id = 5; //< id is constexpr, and it's the only constexpr here.
   return id;
 }
};

int main(){
 test t; //< this cannot be constexpr because it's a variable
         //< (which means it is NOT an expression!)
 std::cout << t.getId() << std::endl;
 return 0;
}

Regardons maintenant godbolt: https: //godbolt.org/z/4naDY3 Nous pouvons voir que le code pour main se résume à:

#include <iostream>

class test{
 public:
 constexpr int getId(){
   constexpr int id = 5;
   return id;
 }
};

int main()
{
    constexpr int G = test().getId();
    std::cout << G << std::endl;
    return 0;
}

Il a donc évalué au moment de la compilation 5 * 22, soit 110. Vous n'aimerez peut-être pas la façon dont constexpr les règles fonctionnent (elles sont un peu maladroites), mais constexpr fonctionne.


8 commentaires

C'est vrai, mais vous ne le faites pas correctement. Vous pouvez UNIQUEMENT utiliser constexpr dans une expression. Déclarer test comme une variable signifie qu'il ne s'agit pas d'une expression, ce qui signifie qu'il ne peut pas être constexpr. Cependant, si vous déclarez une temp dans une expression (d'où test () dans mon code ci-dessus), cela fonctionne.


donc dans mon exemple complet ci-dessus, constexpr est simplement ignoré?


vous n'avez pas déclaré getId comme constexpr. Les expressions doivent être évaluées sur une seule ligne. Le déplacement de la variable de test hors de l'évaluation de l'expression (vers la ligne ci-dessus) interrompt constexpr. L'exemple que j'ai écrit ci-dessus, constexpr évalue tout le chemin jusqu'à G. Allez et regardez de plus près ce que je fais (évaluer G comme constexpr), vs ce que vous faites (évaluer id comme constexpr uniquement).


C'est vrai, mais ma question est, bien que la façon dont je le fais soit mauvaise, qu'est-ce que cela signifie? Pourquoi le compilateur ne se plaint-il pas? il l'ignore simplement à ce stade?


Il fait exactement ce que vous lui avez demandé. id est constexpr, et littéralement rien d'autre. Donc constexpr travaille ici. Il se trouve que tout le reste du code n'est pas constexpr.


ok alors voici la dernière question, elle fait ce que je demande et marque id comme constexpr , bien que ce ne soit pas statique. Alors, à quoi appartient aussi id ? pendant la compilation, il dit "ok nous avons une variable appelée id avec la valeur 5" mais pendant la compilation la classe test n'existe même pas encore. il ne prend vie que pendant l'exécution. comment est-il même compiler.


id n'appartient nulle part, il sera simplement dupliqué partout. Les instructions x64 préfèrent charger des constantes à partir de décalages 8 bits à partir de l'instruction courante (ou du registre de trame de pile). Faire cela coûte moins cher que de charger une constante avec un offset 32 ​​bits (code machine plus petit en gros). Le compilateur essaiera d'insérer que la constante + ou - 128 octets à partir de l'emplacement de l'instruction. Cela signifie qu'il le copiera plusieurs fois lors de son utilisation. static implique une variable qui peut être modifiée, ce qui implique qu'il doit y avoir une copie. constexpr ne fonctionne pas comme ça.


@Josh dans ce cas, votre exemple est trop trivial pour voir toute la puissance de constexpr . Tout ce qui risque de se produire est que getId se compilera en return 5; et n'importe quel compilateur décent le ferait de toute façon .



0
votes

La source de votre confusion est probablement de penser à " constexpr " comme "évalué à la compilation". Bien qu'il y ait du vrai dans cette idée, elle est imprécise. Je vous invite à abandonner votre compréhension actuelle de " constexpr ". À la place, commencez à considérer " constexpr " comme " autorisé dans expressions constantes ". Le langage fournit une définition précise de «expression constante»; pour les besoins actuels, il peut suffire de considérer une expression constante comme une expression dont la valeur peut être calculée par le compilateur. (Si le compilateur calcule cette valeur, il devra évaluer quelque chose au moment de la compilation. Cela conduit à cette notion imprécise de constexpr.)

En adaptant votre réflexion à cette nouvelle vue, concentrez-vous sur le mot «autorisé». Une personne titulaire d'un permis de conduire est autorisée à conduire une voiture, mais il n'est pas interdit de faire du vélo. Une variable avec le spécificateur constexpr est autorisée dans les expressions constantes mais n'est pas interdite à partir d'autres expressions.

<₹Avertissement : Lorsqu'il s'agit spécifiquement de variables entières, le spécificateur constexpr syntaxiquement ne signifie pas beaucoup plus que const . Si une variable entière est à la fois déclarée const et initialisée par une expression constante, alors cette variable reçoit le même privilège qu'une constexpr - elle est autorisée dans les expressions constantes. Il s'agit d'un cas spécial limité qui prend en charge le code pré-C ++ 11. Pour exprimer clairement l'intention (sémantique) dans un nouveau code, toute variable utilisée dans une expression constante doit être constexpr même si la syntaxe du langage lui permet d'être simplement const .

Pendant la compilation, test n'existe pas encore, cependant, le code ci-dessus se compile très bien.

Oui, le code sans erreur de syntaxe a tendance à se compiler très bien. ;)

Pour les variables, la seule limitation imposée par constexpr qui n'est pas imposée par const est que l'initialisation de la variable doit être une expression constante (c'est-à-dire que la valeur doit être quelque chose compilateur peut calculer). Vous l'avez satisfait en initialisant id à 5 plutôt qu'à une valeur déterminée au moment de l'exécution. Votre utilisation de id après l'initialisation est cohérente avec l'utilisation d'un const int , de sorte que l'utilisation est valide pour un constexpr int .

Si test n’existe pas encore, comment getId peut-il exister pendant la compilation?

Je suppose que cela dépend de ce que vous entendez par "exister". Dans un sens, une fonction existe à partir du moment où le compilateur génère son code objet. Ce code est incorporé dans l'exécutable final. Le code objet n'a pas besoin d'être construit pendant l'exécution (uniquement chargé à partir du disque), on pourrait donc dire que la fonction a commencé à exister avant l'exécution du programme. Il y a des mises en garde, mais l'idée générale est valable.

De la même manière, on pourrait dire que la classe test existe lors de la compilation, mais je suppose que vous vouliez dire que les instanciations (objets) de test n'existent pas encore .

Si vous aviez en tête une autre signification pour "exister", alors votre affirmation selon laquelle getId existe pendant la compilation est peut-être fausse. (En particulier, getId n'est pas exécuté lors de la compilation.)

[D'après un commentaire:] Que signifie le fait de dire que id est évalué au moment de la compilation?

Correction: id peut être évalué au moment de la compilation. Il peut également être évalué au moment de l'exécution. Ce qui se passe réellement dépend de la manière dont id est utilisé. Si vous ne donnez pas au compilateur de raison d'évaluer id , il n'est pas obligé de le faire.

[À partir d'un commentaire:] comment peut-il évaluer id pendant la compilation si getId (le parent) n'est évalué que pendant l'exécution .

Le seul endroit où id est utilisé dans votre code est dans une instruction return . Le compilateur est libre de remplacer return id; par return 5; lorsqu'il génère le code objet pour getId . (Ce n'est pas un exemple particulièrement intéressant, mais techniquement une évaluation de id au moment de la compilation. Un exemple plus intéressant utiliserait id dans un contexte qui nécessite une expression constante.)


0 commentaires

0
votes

Pendant la compilation, le test n'existe pas encore, cependant, le code ci-dessus se compile très bien. Si le test n'existe pas encore, comment getId peut-il exister pendant la compilation?

Et si je vous disais que ce code pourrait fonctionner même si l'objet test n'existait pas en temps réel? Je parie que ce programme vous imprimera 5:

 int getId(){
   enum { id = 5 };
   return id;
 }

 int getId(){
   #define id 5
   return id;
 }

Bien sûr, vous ne devriez jamais écrire de code comme celui-ci, mais cela peut vous aider à comprendre de ce qu'est réellement la fonction membre.

Vous pouvez considérer une fonction membre comme une fonction régulière avec un état d'objet (variables membres) qui lui est passé.

constexpr int id = 5;
Voir, un état d'objet n'est pas utilisé dans la fonction getId () . Donc, vous n'avez pas du tout besoin d'une instance d'objet test pour exécuter getId () .

En parlant de constexpr

class test{
 public:
};

int getId(test* obj){
  constexpr int id = 5;
  return id;
}

Ceci est juste une manière moderne d'écrire nommée constante ... Vous pouvez utiliser macro ou enum pour obtenir le même résultat, et vous n'avez pas besoin de C ++ 11 ou même C ++ (C suffit).

int main(){
 test* t = nullptr;
 std::cout << t->getId() << std::endl;
 return 0;
}

Donc, du point de vue du compilateur, getID () est juste une fonction qui renvoie 5 Cela signifie qu'il peut être remplacé par une constante entière équivalente 5. Donc, il insérera en fait 5 au lieu de l'appel getId () .


0 commentaires