Quand devez-vous utiliser la capacité de constexpr en C++11?
il me semble qu'avoir une" fonction qui renvoie toujours 5 "brise ou dilue le sens de"appeler une fonction". Il doit y avoir une raison, ou un besoin pour cette capacité ou elle ne serait pas en C++11. Pourquoi est-il 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 j'arrivais à une révision de code, quelqu'un me dirait, je devrais alors, déclarer une valeur constante au lieu d'écrire retour 5.
13 réponses
supposez que ça fasse quelque chose d'un peu plus compliqué.
constexpr int MeaningOfLife ( int a, int b ) { return a * b; }
const int meaningOfLife = MeaningOfLife( 6, 7 );
Maintenant, vous avez quelque chose qui peut être évalué à une constante tout en maintenant une bonne lisibilité et en permettant un traitement légèrement plus complexe que le simple réglage d'une constante à un nombre.
il fournit essentiellement une bonne aide à la maintenabilité comme il devient plus évident ce que vous faites. Prenons 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 à 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 puissiez savoir que 180 degrés est en radians, il est beaucoup plus clair écrit comme suit:
const float oneeighty = DegreesToRadians( 180.0f );
Beaucoup de bonnes infos ici:
Introduction
constexpr
n'a pas été présenté comme un moyen de dire à l'implémentation que quelque chose peut être évalué dans un contexte qui nécessite une constante-expression ; les implémentations conformes ont pu le prouver avant C++11.
ce qu'une implémentation ne peut pas prouver est l'intention d'un certain morceau de code:
- qu'est-Ce que ce que les développeurs veulent exprimer avec cette entité?
- devrions-nous laisser aveuglément le code être utilisé dans un constante-expression , juste parce que cela fonctionne?
que serait le monde sans constexpr
?
disons que vous développez une bibliothèque et réalisez que vous voulez être en mesure de calculer la somme de chaque entier dans l'intervalle (0,N]
.
int f (int n) {
return n > 0 ? n + f (n-1) : n;
}
L'absence d'intention
un compilateur peut facilement prouver que la fonction ci-dessus est appelable dans un constante - expression si l'argument passé est connu pendant la traduction; mais vous n'avez pas déclaré cela comme une intention-il s'est juste produit pour être le cas.
maintenant quelqu'un d'autre arrive, lit votre fonction, fait la même analyse que le compilateur; " Oh, ce la fonction est utilisable dans une expression constante!" , et écrit le morceau de code suivant.
T arr[f(10)]; // freakin' magic
l'optimisation
vous, en tant que " impressionnant " développeur de bibliothèque, décidez que f
devrait mettre en cache le résultat lorsqu'il est invoqué; qui voudrait calculer le même ensemble de valeurs encore et encore?
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 présentant votre stupide optimisation, vous venez de casser chaque utilisation de votre fonction qui s'est avéré être dans un contexte où un constante-expression était nécessaire.
vous n'avez jamais promis que la fonction était utilisable dans un constante-expression , et sans constexpr
il n'y aurait aucun moyen de fournir une telle promesse.
alors, pourquoi avons-nous besoin de constexpr
?
le l'usage principal de constexpr est de déclarer intention .
si une entité n'est pas marquée comme constexpr
- il n'a jamais été prévu pour être utilisé dans un constante-expression ; et même si elle est, nous comptons sur le compilateur pour diagnostiquer un tel contexte (parce qu'il ignore notre intention).
Take std::numeric_limits<T>::max()
: pour quelque raison que ce soit, il s'agit d'une méthode. constexpr
serait bénéfique ici.
un autre exemple: vous voulez déclarer un tableau C (ou un std::array
) qui est aussi grand qu'un autre tableau. La manière de le faire pour le moment est la suivante:
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)];
merci à constexpr
, vous pouvez:
template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
return N;
}
constexpr
les fonctions sont vraiment belles et un excellent ajout à c++. Cependant, vous avez raison que la plupart des problèmes qu'il résout peut être inélégante ont travaillé avec des macros.
cependant, l'une des utilisations de constexpr
n'a pas d'équivalent C++03, constantes dactylographié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;
D'après ce que j'ai lu, le besoin de constexpr vient d'un problème de métaprogrammation. Les classes de traits peuvent avoir des constantes représentées comme des fonctions, think: numeric_limits:: max(). Avec constexpr, ces types de fonctions peuvent être utilisés dans la métaprogrammation, ou comme limites de tableau, etc.
un autre exemple venant du haut de ma tête serait que pour les interfaces de classe, vous pouvez vouloir que les types dérivés définissent leurs propres constantes pour une opération.
Modifier:
après avoir fouillé sur SO, il semble que d'autres ont trouvé certains exemples de ce qui pourrait être possible avec les constexpres.
De Stroustrup un discours au "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
une autre utilisation (non encore mentionnée) est constexpr
constructeurs. Cela permet de créer des constantes de temps de compilation qui ne doivent pas être initialisées pendant l'exécution.
const std::complex<double> meaning_of_imagination(0, 42);
paire qui avec les littérales définies par l'utilisateur et vous avez le plein soutien pour les classes définies par l'utilisateur littéral.
3.14D + 42_i;
il y avait un modèle 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 gabarits et de constructions bizarres avec spécialisation, SFINAE et stuff - mais exactement comme vous écririez une fonction run-time, mais avec la garantie que le résultat sera déterminé en Compilation-time.
Toutefois, notez que:
int fact(unsigned n) {
if (n==1) return 1;
return fact(n-1)*n;
}
int main() {
return fact(10);
}
compiler ceci avec g++ -O3
et vous verrez que fact(10)
est en effet evaulé lors de la compilation!
un compilateur conscient VLA (donc un compilateur C en mode C99 ou un compilateur 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 que c'est du C++ non standard pour le moment- constexpr
ressemble à un moyen de combattre cela (même sans VLA, dans le cas ci - dessus). Et il y a toujours le problème de la nécessité d'avoir des expressions constantes "formelles" comme modèle argument.
viennent de passer d'un projet à c++11 et sont tombés sur une situation parfaitement bonne pour constexpr qui nettoie les méthodes alternatives d'effectuer la même opération. Le point clé ici est que vous ne pouvez placer la fonction dans la déclaration de la taille du tableau que lorsqu'elle est déclarée constexpr. Il y a un certain nombre de situations où je peux voir que c'est très utile pour aller de l'avant avec le domaine du code dans lequel je suis impliqué.
constexpr size_t GetMaxIPV4StringLength()
{
return ( sizeof( "255.255.255.255" ) );
}
void SomeIPFunction()
{
char szIPAddress[ GetMaxIPV4StringLength() ];
SomeIPGetFunction( szIPAddress );
}
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];
au-dessus de
int arr[5];
parce que c'est plus supportable. L'utilisation de constexpr est beaucoup, beaucoup plus rapide à écrire et à lire que les techniques de métaprogrammation existantes.
Toutes les autres réponses sont grands, je veux juste donner une cool exemple d'une chose que vous pouvez faire avec constexpr c'est incroyable. Voir-Phit ( ) https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) est un analyseur de temps HTML et un moteur de template. Cela signifie que vous pouvez mettre HTML dans et sortir un arbre qui est capable d'être manipulé. Avoir l'analyse faite à la compilation peut vous donner un peu de performance supplémentaire.
du github exemple de page:
#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;
}
il peut permettre de nouvelles optimisations. const
est traditionnellement une indication pour le système de type, et ne peut pas être utilisé pour l'optimisation (par exemple, une fonction de membre const
peut const_cast
et modifier l'objet de toute façon, légalement, de sorte que const
ne peut pas être fiable pour l'optimisation).
constexpr
signifie l'expression vraiment est constante, à condition que les entrées de la fonction sont const. Considérons:
class MyInterface {
public:
int GetNumber() const = 0;
};
si cela est exposé dans un autre module, le compilateur ne peut pas croire que GetNumber()
ne retournera pas des valeurs différentes chaque fois qu'il est appelé - même consécutivement sans aucun appel non-const entre - temps-parce que const
aurait pu être rejeté dans l'implémentation. (Évidemment tout programmeur qui a fait cela devrait être tourné, mais le langage le permet, donc le compilateur doit respecter les règles.)
ajouter constexpr
:
class MyInterface {
public:
constexpr int GetNumber() const = 0;
};
le compilateur peut maintenant appliquer une optimisation où la valeur de retour de GetNumber()
est mise en cache et éliminer les appels supplémentaires à GetNumber()
, parce que constexpr
est une garantie plus forte que la valeur de retour ne changera pas.
c'est utile pour quelque chose comme
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
int some_arr[MeaningOfLife()];
liez ceci avec une classe de traits ou similaire et il devient tout à fait utile.