396 votes

Quand faut-il utiliser la capacité constexpr en C++11 ?

Il me semble que le fait d'avoir une "fonction qui renvoie toujours 5" rompt ou dilue le sens de l'expression "appeler une fonction". Il doit y avoir une raison, ou un besoin pour cette capacité, sinon elle ne serait pas dans C++11. Pourquoi est-elle là ?

// preprocessor.
#define MEANING_OF_LIFE 42

// constants:
const int MeaningOfLife = 42;

// constexpr-function:
constexpr int MeaningOfLife () { return 42; }

Il me semble que si j'écrivais une fonction qui renvoie une valeur littérale, et que je me présentais à une revue de code, quelqu'un me dirait que je devrais alors déclarer une valeur constante au lieu d'écrire return 5.

29 votes

Pouvez-vous définir une fonction récursive qui renvoie un constexpr ? Si oui, je peux en voir l'utilité.

31 votes

Je pense que la question devrait être "pourquoi introduire un nouveau mot-clé ( !) si le compilateur peut déduire par lui-même si une fonction peut être évaluée au moment de la compilation ou non". L'avoir "garanti par un mot-clé" semble bien, mais je pense que je préférerais l'avoir garanti chaque fois que c'est possible, sans avoir besoin d'un mot-clé.

7 votes

@Kos : Quelqu'un qui est PLUS familier avec les internes de C++ préférerait probablement votre question, mais ma question vient du point de vue d'une personne qui a écrit du code C auparavant, mais qui n'est pas du tout familier avec les mots-clés de C++ 2011, ni avec les détails de l'implémentation du compilateur C++. Être capable de raisonner sur l'optimisation du compilateur et la déduction des expressions constantes est un sujet pour une question d'utilisateur plus avancé que celle-ci.

355voto

Goz Points 35007

Supposons qu'il fasse quelque chose d'un peu plus compliqué.

constexpr int MeaningOfLife ( int a, int b ) { return a * b; }

const int meaningOfLife = MeaningOfLife( 6, 7 );

Vous avez maintenant quelque chose qui peut être évalué jusqu'à une constante tout en conservant une bonne lisibilité et en permettant un traitement légèrement plus complexe que la simple définition d'une constante à un nombre.

Il s'agit essentiellement d'une bonne aide à la maintenabilité, car ce que vous faites devient plus évident. Prenez max( a, b ) par exemple :

template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }

C'est un choix assez simple, mais cela signifie que si vous appelez max avec des valeurs constantes, il est explicitement calculé au moment de la compilation et non au moment de l'exécution.

Un autre bon exemple serait un DegreesToRadians fonction. Tout le monde trouve les degrés plus faciles à lire que les radians. Même si vous savez que 180 degrés correspondent à 3,14159265 (Pi) en radians, il est beaucoup plus clair de l'écrire comme suit :

const float oneeighty = DegreesToRadians( 180.0f );

Beaucoup de bonnes informations ici :

http://en.cppreference.com/w/cpp/language/constexpr

21 votes

Excellente remarque sur le fait qu'il indique au compilateur d'essayer de calculer la valeur au moment de la compilation. Je suis curieux de savoir pourquoi const ne fournit pas cette fonctionnalité lorsque des optimisations spécifiques sont spécifiées ? Ou le fait-il ?

12 votes

@Tamus : Souvent, il le fera, mais il n'est pas obligé de le faire. constexpr oblige le compilateur et lui enverra une erreur s'il ne peut pas le faire.

22 votes

Je le vois maintenant. Sin(0,5) en est une autre. Cela remplace proprement les macros C.

95voto

Konrad Rudolph Points 231505

Prenez std::numeric_limits<T>::max() pour une raison quelconque, c'est une méthode. constexpr serait bénéfique ici.

Un autre exemple : vous voulez déclarer un tableau C (ou un fichier std::array ) qui est aussi grand qu'un autre tableau. Pour l'instant, la façon de procéder est la suivante :

int x[10];
int y[sizeof x / sizeof x[0]];

Mais ne serait-ce pas mieux d'être capable d'écrire :

int y[size_of(x)];

Merci à constexpr vous pouvez :

template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
    return N;
}

1 votes

+1 pour une bonne utilisation des templates, mais cela fonctionnerait exactement de la même manière sans constexpr, n'est-ce pas ?

23 votes

@Kos : Non. Il renverrait une valeur d'exécution. constexpr oblige le compilateur à faire en sorte que la fonction renvoie une valeur au moment de la compilation (si elle le peut).

16 votes

@Kos : sans le constexpr il ne peut pas être utilisé dans une déclaration de taille de tableau, ni comme argument de modèle, que le résultat de l'appel de fonction soit une constante de temps de compilation ou non. Ces deux cas sont essentiellement les seuls cas d'utilisation de la fonction constexpr mais au moins le cas d'utilisation de l'argument du modèle est assez important.

20voto

deft_code Points 19418

constexpr Les fonctions sont vraiment intéressantes et constituent un excellent complément au C++. Cependant, vous avez raison de dire que la plupart des problèmes qu'elles résolvent peuvent être contournés de manière inélégante avec des macros.

Cependant, l'une des utilisations de constexpr n'a pas d'équivalent C++03, des constantes typées.

// This is bad for obvious reasons.
#define ONE 1;

// This works most of the time but isn't fully typed.
enum { TWO = 2 };

// This doesn't compile
enum { pi = 3.1415f };

// This is a file local lvalue masquerading as a global
// rvalue.  It works most of the time.  But May subtly break
// with static initialization order issues, eg pi = 0 for some files.
static const float pi = 3.1415f;

// This is a true constant rvalue
constexpr float pi = 3.1415f;

// Haven't you always wanted to do this?
// constexpr std::string awesome = "oh yeah!!!";
// UPDATE: sadly std::string lacks a constexpr ctor

struct A
{
   static const int four = 4;
   static const int five = 5;
   constexpr int six = 6;
};

int main()
{
   &A::four; // linker error
   &A::six; // compiler error

   // EXTREMELY subtle linker error
   int i = rand()? A::four: A::five;
   // It not safe use static const class variables with the ternary operator!
}

//Adding this to any cpp file would fix the linker error.
//int A::four;
//int A::six;

1 votes

La norme définit-elle vraiment std::string comme ayant un constructeur constexpr ? (En tout cas, cela ne fonctionne pas avec GCC 4.6, qui supporte par ailleurs constexpr).

1 votes

Non, la plupart des constructeurs de std::strings ne peut pas être constexrp, ils ont besoin du tas d'exécution.

0 votes

@MooingDuck, malheureusement vous avez raison. std::string ne recevront pas de constexpr ctor. Cela pourrait certainement être fait, et probablement à moindre coût. Mais je ne sais pas si cela pourrait être fait de manière suffisamment bon marché pour la bibliothèque standard (ne payez pas pour ce que vous n'utilisez pas).

15voto

luke Points 16255

D'après ce que j'ai lu, le besoin de constexpr provient d'un problème de métaprogrammation. Les classes de traits peuvent avoir des constantes représentées sous forme de fonctions, par exemple : numeric_limits::max(). Avec constexpr, ces types de fonctions peuvent être utilisés dans la métaprogrammation, ou comme limites de tableaux, etc. etc.

Un autre exemple qui me vient à l'esprit est que pour les interfaces de classe, vous pouvez souhaiter que les types dérivés définissent leurs propres constantes pour certaines opérations.

Edit :

Après avoir farfouillé sur SO, il semble que d'autres personnes aient proposé un peu de exemples de ce qui pourrait être possible avec constexprs.

0 votes

"Pour faire partie d'une interface, il faut être une fonction" ?

0 votes

Maintenant que je peux en voir l'utilité, je suis un peu plus enthousiaste à propos de C++ 0x. Cela semble être une chose bien pensée. Je savais qu'ils devaient l'être. Ces uber-geeks des standards de langage font rarement des choses aléatoires.

0 votes

Je suis bien plus excité par les lambdas, le modèle de threading, les initializer_list, les références rvalue, les modèles variadiques, les nouvelles surcharges bind... il y a pas mal de choses à attendre.

11voto

user2176127 Points 428

Extrait du discours de Stroustrup à "Going Native 2012" :

template<int M, int K, int S> struct Unit { // a unit in the MKS system
       enum { m=M, kg=K, s=S };
};

template<typename Unit> // a magnitude with a unit 
struct Value {
       double val;   // the magnitude 
       explicit Value(double d) : val(d) {} // construct a Value from a double 
};

using Speed = Value<Unit<1,0,-1>>;  // meters/second type
using Acceleration = Value<Unit<1,0,-2>>;  // meters/second/second type
using Second = Unit<0,0,1>;  // unit: sec
using Second2 = Unit<0,0,2>; // unit: second*second 

constexpr Value<Second> operator"" s(long double d)
   // a f-p literal suffixed by ‘s’
{
  return Value<Second> (d);  
}   

constexpr Value<Second2> operator"" s2(long double d)
  // a f-p literal  suffixed by ‘s2’ 
{
  return Value<Second2> (d); 
}

Speed sp1 = 100m/9.8s; // very fast for a human 
Speed sp2 = 100m/9.8s2; // error (m/s2 is acceleration)  
Speed sp3 = 100/9.8s; // error (speed is m/s and 100 has no unit) 
Acceleration acc = sp1/0.5s; // too fast for a human

2 votes

Cet exemple se trouve également dans l'article de Stroustrup. Développement de logiciels pour l'infrastructure .

0 votes

Clang-3.3 : erreur : le type de retour de la fonction constexpr 'Value<Second>' n'est pas un type littéral

0 votes

C'est bien, mais qui met des littéraux dans un code comme celui-ci ? Le fait que votre compilateur "vérifie vos unités" pour vous aurait un sens si vous écriviez une calculatrice interactive.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X