web-dev-qa-db-fra.com

Quand devriez-vous utiliser la fonctionnalité constexpr dans C ++ 11?

Il me semble qu'avoir une "fonction qui retourne toujours 5" brise ou dilue le sens de "appeler une fonction". Il doit y avoir une raison, ou un besoin pour cette capacité ou ce ne serait pas en C++ 11. Pourquoi est-ce 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 revenais à une révision du code, quelqu'un me le dirait, je devrais alors déclarer une valeur constante au lieu d'écrire le retour 5.

315
Warren P

Supposons que cela 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é comme une constante tout en maintenant une bonne lisibilité et en permettant un traitement légèrement plus complexe que la simple définition d'une constante sur un nombre.

Fondamentalement, cela facilite l’entretien, 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 une fonction DegreesToRadians. Tout le monde trouve les degrés plus faciles à lire que les radians. Bien que vous sachiez que 180 degrés est en radians, il est écrit plus clairement:

const float oneeighty = DegreesToRadians( 180.0f );

Beaucoup de bonnes informations ici:

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

284
Goz

Introduction

constexpr n'a pas été introduit comme moyen d'indiquer à l'implémentation que quelque chose peut être évalué dans un contexte qui nécessite un expression-constante; des implémentations conformes ont pu le prouver avant C++ 11.

Quelque chose qu'une implémentation ne puisse pas prouver est le intention d'un certain morceau de code:

  • Qu'est-ce que le développeur veut exprimer avec cette entité?
  • Devrions-nous aveuglément autoriser l’utilisation de code dans un expression constante, simplement parce que cela fonctionne?

Que serait le monde sans constexpr?

Supposons que vous développez une bibliothèque et réalisiez que vous voulez pouvoir calculer la somme de chaque entier compris dans l'intervalle (0,N].

int f (int n) {
  return n > 0 ? n + f (n-1) : n;
}

Le manque d'intention

Un compilateur peut facilement prouver que la fonction ci-dessus est appelable dans un expression-constante si l'argument transmis est connu pendant la traduction; mais vous n'avez pas déclaré cela comme une intention - cela s'est juste avéré être le cas.

Maintenant, quelqu'un d'autre arrive, lit votre fonction, fait la même analyse que le compilateur; "Oh, cette fonction est utilisable dans une expression constante!", et écrit le code suivant.

T arr[f(10)]; // freakin' magic

L'optimisation

En tant que développeur de bibliothèque "génial", vous décidez que f devrait mettre le résultat en cache lors de son invocation; qui voudrait calculer le même ensemble de valeurs à plusieurs reprises?

int func (int n) { 
  static std::map<int, int> _cached;

  if (_cached.find (n) == _cached.end ()) 
    _cached[n] = n > 0 ? n + func (n-1) : n;

  return _cached[n];
}

Le résultat

En introduisant votre optimisation stupide, vous venez de casser toutes les utilisations de votre fonction qui se trouvaient dans un contexte où un expression constante était requis.

Vous n'avez jamais promis que la fonction était utilisable dans un expression constante, et sans constexpr, il n'y aurait aucun moyen de fournir une telle promesse.


Alors, pourquoi avons-nous besoin de constexpr?

L'utilisation principale de constexpr est de déclarer intention.

Si une entité n'est pas marquée comme constexpr - elle n'a jamais été destinée à être utilisée dans un expression constante; et même si c'est le cas, nous comptons sur le compilateur pour diagnostiquer ce contexte (car il ne tient pas compte de notre intention).

134

Prenez std::numeric_limits<T>::max(): quelle que soit la raison, il s'agit d'une méthode. constexpr serait bénéfique ici.

Autre exemple: vous voulez déclarer un tableau C (ou un std::array) aussi grand qu'un autre tableau. La façon de faire cela en ce moment est comme suit:

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

Mais ne serait-il pas préférable de pouvoir écrire:

int y[size_of(x)];

Grâce à constexpr, vous pouvez:

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

Les fonctions constexpr sont vraiment agréables et constituent un excellent ajout à c ++. Cependant, vous avez raison de dire que la plupart des problèmes qu’elle résout peuvent être résolus de manière non élégante avec des macros.

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

// 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;
18
deft_code

D'après ce que j'ai lu, le besoin de constexpr provient d'un problème de métaprogrammation. Les classes de caractères peuvent avoir des constantes représentées sous forme de fonctions, pensez: limites_numérique :: max (). Avec constexpr, ces types de fonctions peuvent être utilisés en métaprogrammation, ou en tant que limites de tableau, etc., etc.

Un autre exemple qui me vient à l’esprit serait que, pour les interfaces de classe, vous souhaitiez peut-être que les types dérivés définissent leurs propres constantes pour certaines opérations.

Modifier:

Après avoir fouillé SO, il semble que d’autres aient mis au point certainsexemples de ce qui pourrait être possible avec constexprs.

14
luke

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
11
user2176127

Une autre utilisation (non encore mentionnée) est constexpr constructors. Cela permet de créer des constantes de temps de compilation qui n'ont pas besoin d'être initialisées lors de l'exécution.

const std::complex<double> meaning_of_imagination(0, 42); 

Associez-le à des littéraux définis par l'utilisateur et vous bénéficierez d'une prise en charge complète des classes littérales définies par l'utilisateur.

3.14D + 42_i;
7
Motti

Il y avait un motif avec métaprogrammation:

template<unsigned T>
struct Fact {
    enum Enum {
        VALUE = Fact<T-1>*T;
    };
};

template<>
struct Fact<1u> {
    enum Enum {
        VALUE = 1;
    };
};

// Fact<10>::VALUE is known be a compile-time constant

Je crois que constexpr a été introduit pour vous permettre d'écrire de telles constructions sans avoir besoin de modèles ni de constructions étranges avec spécialisation, SFINAE et d'autres choses - mais exactement comme si vous écriviez une fonction d'exécution, mais avec la garantie que le résultat sera déterminé au moment de la compilation.

Cependant, notez que:

int fact(unsigned n) {
    if (n==1) return 1;
    return fact(n-1)*n;
}

int main() {
    return fact(10);
}

Compilez ceci avec g++ -O3 et vous verrez que fact(10) est en effet évalué au moment de la compilation!

Un compilateur compatible VLA (donc un compilateur C en mode C99 ou C++ avec des extensions C99) peut même vous permettre de faire:

int main() {
    int tab[fact(10)];
    int tab2[std::max(20,30)];
}

Mais le fait que ce soit du C++ non standard pour le moment - constexpr ressemble à un moyen de lutter contre cela (même sans VLA, dans le cas précédent). Et il reste le problème de la nécessité d'avoir des expressions constantes "formelles" comme arguments de modèle.

6
Kos

Nous venons juste de passer d'un projet à c ++ 11 et nous sommes tombés sur une situation idéale pour constexpr, qui nettoie d'autres méthodes permettant d'exécuter la même opération. Le point clé ici est que vous ne pouvez placer la fonction dans la déclaration de taille de tableau que si elle est déclarée constexpr. Il existe un certain nombre de situations dans lesquelles je vois que cela est très utile pour progresser dans la zone de code dans laquelle je suis impliqué.

constexpr size_t GetMaxIPV4StringLength()
{
    return ( sizeof( "255.255.255.255" ) );
}

void SomeIPFunction()
{
    char szIPAddress[ GetMaxIPV4StringLength() ];
    SomeIPGetFunction( szIPAddress );
}
5
jgibbs

Toutes les autres réponses sont excellentes, je veux juste donner un exemple intéressant d'une chose que vous pouvez faire avec constexpr qui est incroyable. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) est un analyseur HTML et un moteur de template pour la compilation. Cela signifie que vous pouvez insérer du code HTML et obtenir un arbre pouvant être manipulé. Faire analyser au moment de la compilation peut vous donner un peu plus de performance.

Dans l'exemple de la page github:

#include <iostream>
#include "seephit.h"
using namespace std;



int main()
{
  constexpr auto parser =
    R"*(
    <span >
    <p  color="red" height='10' >{{name}} is a {{profession}} in {{city}}</p  >
    </span>
    )*"_html;

  spt::tree spt_tree(parser);

  spt::template_dict dct;
  dct["name"] = "Mary";
  dct["profession"] = "doctor";
  dct["city"] = "London";

  spt_tree.root.render(cerr, dct);
  cerr << endl;

  dct["city"] = "New York";
  dct["name"] = "John";
  dct["profession"] = "janitor";

  spt_tree.root.render(cerr, dct);
  cerr << endl;
}
2
Halcyon

Votre exemple de base sert le même argument que celui des constantes elles-mêmes. Pourquoi utiliser

static const int x = 5;
int arr[x];

plus de

int arr[5];

Parce que c'est beaucoup plus facile à maintenir. Utiliser constexpr est beaucoup, beaucoup plus rapide à écrire et à lire que les techniques de métaprogrammation existantes.

1
Puppy

Quand utiliser constexpr:

  1. chaque fois qu'il y a une constante de temps de compilation.
0
BreakBadSP

Cela peut permettre de nouvelles optimisations. const est traditionnellement un indice pour le système de types et ne peut pas être utilisé pour l'optimisation (par exemple, une fonction membre const peut const_cast et modifier l'objet de toute façon, légalement, donc const on ne peut pas faire confiance pour l'optimisation).

constexpr signifie que l'expression vraiment est constante, à condition que les entrées de la fonction soient const. Considérer:

class MyInterface {
public:
    int GetNumber() const = 0;
};

Si cela est exposé dans un autre module, le compilateur ne peut pas croire que GetNumber() ne renverra pas de valeurs différentes chaque fois qu'il sera appelé - même consécutivement sans appels non-const entre eux - parce que const pourrait ont été jetés dans la mise en œuvre. (Évidemment, tout programmeur qui a fait cela devrait être tourné, mais le langage le permet, donc le compilateur doit respecter les règles.)

Ajout de constexpr:

class MyInterface {
public:
    constexpr int GetNumber() const = 0;
};

Le compilateur peut maintenant appliquer une optimisation dans laquelle la valeur de retour de GetNumber() est mise en cache et éliminer les appels supplémentaires à GetNumber(), car constexpr constitue une garantie plus forte que la valeur de retour ne changera pas.

0
AshleysBrain