¿Debo declarar una constante en lugar de escribir una función constexpr?
Me parece que tener una "función que siempre devuelve 5" es romper o diluir el significado de "llamar a una función". Debe haber una razón o una necesidad para esta capacidad o no estaría en C++ 11. ¿Por qué está ahí?
// preprocessor.
#define MEANING_OF_LIFE 42
// constants:
const int MeaningOfLife = 42;
// constexpr-function:
constexpr int MeaningOfLife () { return 42; }
Me parece que si escribiera una función que devuelve un valor literal y llegara a una revisión de código, alguien me diría que debería declarar un valor constante en lugar de escribir retorno 5.
Supongamos que hace algo un poco más complicado.
constexpr int MeaningOfLife ( int a, int b ) { return a * b; }
const int meaningOfLife = MeaningOfLife( 6, 7 );
Ahora tiene algo que se puede evaluar hasta una constante manteniendo una buena legibilidad y permitiendo un procesamiento un poco más complejo que simplemente establecer una constante en un número.
Básicamente proporciona una buena ayuda para el mantenimiento a medida que resulta más obvio lo que estás haciendo. Toma max( a, b )
por ejemplo:
template< typename Type > constexpr Type max( Type a, Type b ) { return a < b ? b : a; }
Es una elección bastante simple, pero significa que si llama max
con valores constantes, se calcula explícitamente en tiempo de compilación y no en tiempo de ejecución.
Otro buen ejemplo sería una DegreesToRadians
función. Todo el mundo encuentra que los grados son más fáciles de leer que los radianes. Si bien quizás sepas que 180 grados es 3,14159265 (Pi) en radianes, es mucho más claro escribirlo de la siguiente manera:
const float oneeighty = DegreesToRadians( 180.0f );
Aquí hay mucha información útil:
http://en.cppreference.com/w/cpp/language/constexpr
Introducción
constexpr
no se introdujo como una forma de decirle a la implementación que algo se puede evaluar en un contexto que requiere una expresión constante ; Las implementaciones conformes han podido demostrar esto antes de C++ 11.
Algo que una implementación no puede probar es la intención de un determinado fragmento de código:
- ¿Qué es lo que el desarrollador quiere expresar con esta entidad?
- ¿Deberíamos permitir ciegamente que el código se use en una expresión constante , solo porque funciona?
¿Qué sería del mundo sin él constexpr
?
Digamos que está desarrollando una biblioteca y se da cuenta de que desea poder calcular la suma de cada número entero en el intervalo (0,N]
.
int f (int n) {
return n > 0 ? n + f (n-1) : n;
}
La falta de intención
Un compilador puede probar fácilmente que la función anterior se puede llamar en una expresión constante si el argumento pasado se conoce durante la traducción; pero no ha declarado esto como una intención; simplemente resultó ser el caso.
Ahora viene alguien más, lee su función, hace el mismo análisis que el compilador; "¡ Oh, esta función se puede utilizar en una expresión constante!" y escribe el siguiente fragmento de código.
T arr[f(10)]; // freakin' magic
la optimizacion
Usted, como desarrollador de biblioteca, decide que f
debe almacenar en caché el resultado cuando se invoca; ¿Quién querría calcular el mismo conjunto de valores una y otra vez?
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];
}
El resultado
Al introducir su optimización, simplemente interrumpió cada uso de su función que se encontraba en un contexto donde se requería una expresión constante .
Nunca prometiste que la función fuera utilizable en una expresión constante , y sin constexpr
ella no habría forma de cumplir dicha promesa.
Entonces, ¿por qué necesitamos constexpr
?
El uso principal de constexpr es declarar intención .
Si una entidad no está marcada como constexpr
, nunca estuvo destinada a usarse en una expresión constante ; e incluso si lo es, confiamos en el compilador para diagnosticar dicho contexto (porque ignora nuestra intención).
Tomar std::numeric_limits<T>::max()
: por alguna razón, este es un método. constexpr
Sería beneficioso aquí.
Otro ejemplo: desea declarar una matriz C (o una std::array
) que sea tan grande como otra matriz. La forma de hacer esto en este momento es la siguiente:
int x[10];
int y[sizeof x / sizeof x[0]];
Pero, ¿no sería mejor poder escribir:
int y[size_of(x)];
Gracias a constexpr
, puedes:
template <typename T, size_t N>
constexpr size_t size_of(T (&)[N]) {
return N;
}
constexpr
Las funciones son realmente agradables y una gran adición a C++. Sin embargo, tiene razón en que la mayoría de los problemas que resuelve se pueden solucionar de manera poco elegante con macros.
Sin embargo, uno de los usos de constexpr
no tiene constantes escritas equivalentes en 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;