¿Cuándo debe usar la capacidad constexpr en C ++ 11?


337

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 de 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 escribí una función que devuelve un valor literal, y llegué a una revisión de código, alguien me diría, entonces debería declarar un valor constante en lugar de escribir return 5.


28
¿Puedes definir una función recursiva que devuelva a constexpr? Si es así, puedo ver un uso.
antes del

20
Creo que la pregunta debería indicar "¿por qué introducir una nueva palabra clave (!) Si el compilador puede deducir por sí mismo si una función se puede evaluar en tiempo de compilación o no". Tenerlo "garantizado por una palabra clave" suena bien, pero creo que preferiría tenerlo garantizado siempre que sea posible, sin necesidad de una palabra clave.
Kos

66
@Kos: Alguien que esté MÁS familiarizado con los componentes internos de C ++ probablemente preferiría su pregunta, pero mi pregunta proviene de una persona que ha escrito código C anteriormente, pero que no está familiarizado con las palabras clave de C ++ 2011, ni con los detalles de implementación del compilador de C ++ . Ser capaz de razonar sobre la optimización del compilador y la deducción de expresión constante es un tema para una pregunta de usuario más avanzada que esta.
Warren P

8
@Kos Estaba pensando en la misma línea que usted, y la respuesta que obtuve fue, sin constexpr, ¿cómo podría (fácilmente) saber que el compilador realmente evaluó la función por usted? Supongo que podría verificar la salida del ensamblaje para ver qué hizo, pero es más fácil decirle al compilador que necesita esa optimización, y si por alguna razón no puede hacer eso por usted, le dará una buena compilación. error en lugar de fallar silenciosamente para optimizar donde esperaba que optimizara.
Jeremy Friesner

3
@Kos: Se podría decir lo mismo al respecto const. De hecho, ¡la intención obligatoria es útil ! Las dimensiones de la matriz son el ejemplo canónico.
Carreras de ligereza en órbita el

Respuestas:


303

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 puede evaluarse 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 la mantenibilidad, ya que se vuelve más obvio lo que está haciendo. Toma max( a, b )por ejemplo:

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

Es una opción bastante simple, pero significa que si llama maxcon valores constantes, se calcula explícitamente en tiempo de compilación y no en tiempo de ejecución.

Otro buen ejemplo sería una DegreesToRadiansfunción. Todos encuentran los grados más fáciles de leer que los radianes. Si bien puede saber que 180 grados está en radianes, está mucho más claro escrito de la siguiente manera:

const float oneeighty = DegreesToRadians( 180.0f );

Aquí hay mucha información útil:

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


18
Excelente punto para decirle al compilador que intente calcular el valor en el momento de la compilación. Tengo curiosidad por qué const no proporciona esta funcionalidad cuando se especifican optimizaciones específicas. O lo hace?
TamusJRoyce

11
@Tamus: A menudo lo hará, pero no está obligado a hacerlo. constexpr obliga al compilador y escupirá un error si no puede.
Goz

20
Ya lo veo. El pecado (0.5) es otro. Esto reemplaza las macros C perfectamente.
Warren P

10
Puedo ver esto como una nueva pregunta de entrevista: explique las diferencias entre la palabra clave const y constexpr.
Warren P

2
Como una forma de documentar este punto por mí mismo, escribí un código similar al anterior y nuevamente con la función "const" en lugar de "constexpr". Como estoy usando Clang3.3, -pedantic-errors y -std = c ++ 11 esperaba que este último no se compilara. Se compiló y ejecutó como en el caso "constexpr". ¿Supones que esta es una extensión clang o ha habido un ajuste en la especificación de C ++ 11 desde que se respondió esta publicación?
Arbalest

144

Introducción

constexprno 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 probar esto antes de C ++ 11.

Algo que una implementación no puede probar es la intención de un cierto código:

  • ¿Qué es lo que el desarrollador quiere expresar con esta entidad?
  • ¿Deberíamos permitir ciegamente que el código se use en un expresión constante , solo porque funciona?

¿Sin qué sería el mundo 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 demostrar fácilmente que la función anterior es invocable 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 tu función, hace el mismo análisis que el compilador; " ¡Oh, esta función es utilizable 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 bibliotecas "impresionante" , decide que fdebe 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 presentar su tonta optimización, rompió cada uso de su función que estaba en un contexto donde una expresión constante se requería .

Nunca prometiste que la función fuera utilizable en una expresión constante , y sin ella constexprno habría forma de proporcionar esa promesa.


Entonces, ¿por qué necesitamos constexpr?

El uso principal de constexpr es declarar intención .

Si una entidad no está marcada como constexpr- nunca fue diseñada para usarse en una expresión constante ; e incluso si es así, confiamos en el compilador para diagnosticar dicho contexto (porque ignora nuestra intención).


25
Esta es probablemente la respuesta correcta, ya que los cambios recientes en C ++ 14 y C ++ 17 permiten que se use un rango mucho más amplio del lenguaje en las constexprexpresiones. En otras palabras, casi cualquier cosa puede ser anotada constexpr(¿tal vez algún día simplemente desaparecerá debido a esto?), Y a menos que uno tenga un criterio de cuándo usarla constexpro no, casi todo el código se escribirá como tal .
alecov

44
@alecov Definitivamente no todo ... I/O, syscally dynamic memory allocationdefinitivamente no se puede marcar como constexprAdemás, no todo debería serlo constexpr.
JiaHao Xu

1
@alecov Algunas funciones están destinadas a ejecutarse en tiempo de ejecución y no tiene sentido hacerlo en tiempo de compilación.
JiaHao Xu

1
También me gusta esta respuesta la mejor. La evaluación del tiempo de compilación es una buena optimización, pero lo que realmente obtienes constexpres una garantía de algún tipo de comportamiento. Justo como lo consthace.
Tomáš Zato - Restablece a Monica el

¿Qué compilador permite que esta versión sin constexpr int f (int n) { return n > 0 ? n + f (n-1) : n;} T arr[f(10)]; no pueda hacer que se compile en ningún lado?
Jer

91

Tome std::numeric_limits<T>::max(): por cualquier razón, este es un método. constexprSería beneficioso aquí.

Otro ejemplo: desea declarar una matriz C (o a std::array) que es tan grande como otra matriz. La forma de hacer esto en este momento es así:

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;
}

1
+1 para un buen uso de la plantilla, pero funcionaría exactamente igual sin constexpr, ¿no?
Kos

21
@Kos: No. Devolvería un valor de tiempo de ejecución. constexprobliga al compilador a hacer que la función devuelva un valor de tiempo de compilación (si puede).
deft_code el

14
@Kos: sin el constexprno se puede usar en una declaración de tamaño de matriz, ni como argumento de plantilla, independientemente de si el resultado de la llamada a la función es una constante de tiempo de compilación o no. Estos dos son básicamente los únicos casos de uso, constexprpero al menos el argumento de caso de uso de plantilla es algo importante.
Konrad Rudolph

2
"por alguna razón, este es un método": la razón es que solo hay enteros de tiempo de compilación en C ++ 03, pero no hay otros tipos de tiempo de compilación, por lo que solo un método puede funcionar para tipos arbitrarios anteriores a C ++ 11.
Sebastian Mach

55
@LwCui No, no está "bien": GCC es laxa por defecto en ciertas cosas. Use la -pedanticopción y se marcará como un error.
Konrad Rudolph el

19

constexprLas 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 pueden solucionarse de manera poco eficiente con macros.

Sin embargo, uno de los usos de constexprno tiene constantes tipeadas equivalentes a 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;

12
¿Podría por favor aclarar que "error de enlace EXTREMADAMENTE sutil"? ¿O al menos proporcionar un puntero a una aclaración?
enobayram

44
@enobayram, el operador ternario toma la dirección de los operandos. Eso no es obvio por el código. Todo se compila bien, pero el enlace falla porque la dirección de fourno se resuelve. Realmente tuve que cavar para averiguar quién estaba tomando la dirección de mistatic const variable.
deft_code

23
"Esto es malo por razones obvias": la razón más obvia es el punto y coma, ¿verdad?
TonyK

44
El "error de enlace EXTREMADAMENTE sutil" me tiene completamente perplejo. Ni fourtampoco fiveestán en el alcance.
Steven Lu

3
vea también el nuevo enum classtipo, corrige algunos de los problemas de enumeración.
ninMonkey

14

Por lo que he leído, la necesidad de constexpr proviene de un problema en la metaprogramación. Las clases de rasgos pueden tener constantes representadas como funciones, piense: numeric_limits :: max (). Con constexpr, esos tipos de funciones se pueden usar en la metaprogramación, o como límites de matriz, etc.

Otro ejemplo fuera de mi cabeza sería que para las interfaces de clase, es posible que desee que los tipos derivados definan sus propias constantes para alguna operación.

Editar:

Después de hurgar en SO, parece que otros han encontrado algunos ejemplos de lo que podría ser posible con constexprs.


"¿Para ser parte de una interfaz tienes que ser una función"?
Daniel Earwicker

Ahora que puedo ver la utilidad de esto, estoy un poco más entusiasmado con C ++ 0x. Parece una cosa bien pensada. Sabía que debían serlo. Esos uber-geeks estándar de lenguaje rara vez hacen cosas al azar.
Warren P

Estoy mucho más entusiasmado con lambdas, el modelo de subprocesos, initializer_list, referencias de valor, plantillas variadic, las nuevas sobrecargas de enlace ... hay muchas cosas que esperar.
luke

1
Ah sí, pero ya entiendo lambdas / cierres en varios otros idiomas. constexpres más específicamente útil en un compilador con un poderoso sistema de evaluación de expresiones en tiempo de compilación. C ++ realmente no tiene pares en ese dominio. (eso es un gran elogio para C ++ 11, en mi humilde opinión)
Warren P

11

Del discurso de Stroustrup en "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
Este ejemplo también se puede encontrar en el software de desarrollo de software para infraestructura de Stroustrup .
Matthieu Poullet

clang-3.3: error: el tipo de retorno de la función constexpr 'Value <Second>' no es un tipo literal
Mitja

Esto es bueno, pero quién pone literales en código como este. Hacer que su compilador "verifique sus unidades" para usted tendría sentido si estuviera escribiendo una calculadora interactiva.
bobobobo

55
@bobobobo o si estaba escribiendo software de navegación para el Mars Climate Orbiter, tal vez :)
Jeremy Friesner

1
Para hacerlo compilar: 1. Use guiones bajos en los sufijos literales. 2. agregue el operador "" _m por 100_m. 3. use 100.0_m, o agregue una sobrecarga que acepte sin firmar largo largo. 4. Declare el constructor Value constexpr. 5. Agregue el operador correspondiente / a la clase Value de esta manera: constexpr auto operator / (const Value <Y> & other) const {return Value <Unit <TheUnit :: m - Value <Y> :: TheUnit :: m, TheUnit :: kg - Valor <Y> :: TheUnit :: kg, TheUnit :: s - Valor <Y> :: TheUnit :: s >> (val / other.val); }. Donde TheUnit es typedef para la Unidad agregada dentro de la clase Value.
0kcats

8

Otro uso (aún no mencionado) son los constexprconstructores. Esto permite crear constantes de tiempo de compilación que no tienen que inicializarse durante el tiempo de ejecución.

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

Empareje eso con literales definidos por el usuario y tendrá soporte completo para clases literales definidas por el usuario.

3.14D + 42_i;

6

Solía ​​haber un patrón con metaprogramación:

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

Creo que constexprse introdujo para permitirle escribir tales construcciones sin la necesidad de plantillas y construcciones extrañas con especialización, SFINAE y otras cosas, pero exactamente como escribiría una función de tiempo de ejecución, pero con la garantía de que el resultado se determinará en la compilación -hora.

Sin embargo, tenga en cuenta que:

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

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

Compila esto g++ -O3y verás quefact(10) efectivamente se evacua en tiempo de compilación!

Un compilador compatible con VLA (por lo tanto, un compilador C en modo C99 o un compilador C ++ con extensiones C99) puede incluso permitirle hacer:

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

Pero por el momento no es C ++ estándar: constexprparece una forma de combatir esto (incluso sin VLA, en el caso anterior). Y todavía existe el problema de la necesidad de tener expresiones constantes "formales" como argumentos de plantilla.


La función de hecho no se evalúa en tiempo de compilación. Debe ser constexpr y debe tener solo una declaración de devolución.
Sumant

1
@Sumant: Tienes razón en que no tiene que evaluarse en tiempo de compilación, ¡pero lo es! Me refería a lo que realmente sucede en los compiladores. Compílelo en el CCG reciente, vea el resultado resultante y compruebe usted mismo si no me cree.
Kos

Intente agregar std::array<int, fact(2)>y verá que fact () no se evalúa en tiempo de compilación. Es solo el optimizador GCC haciendo un buen trabajo.

1
Eso es lo que dije ... ¿estoy realmente tan poco claro? Ver el último párrafo
Kos

5

Acabo de comenzar a cambiar un proyecto a c ++ 11 y encontré una situación perfectamente buena para constexpr que limpia métodos alternativos para realizar la misma operación. El punto clave aquí es que solo puede colocar la función en la declaración de tamaño de matriz cuando se declara constexpr. Hay varias situaciones en las que puedo ver que esto es muy útil para avanzar con el área de código en la que estoy involucrado.

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

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

44
Esto también podría escribirse: const size_t MaxIPV4StringLength = sizeof ("255.255.255.255");
Superfly Jon

static inline constexpr const autoProbablemente es mejor.
JiaHao Xu

3

Todas las otras respuestas son geniales, solo quiero dar un ejemplo genial de una cosa que puedes hacer con constexpr que es increíble. See-Phit ( https://github.com/rep-movsd/see-phit/blob/master/seephit.h ) es un motor de análisis y plantillas HTML en tiempo de compilación. Esto significa que puede poner HTML y sacar un árbol que pueda ser manipulado. Hacer el análisis en tiempo de compilación puede brindarle un poco de rendimiento adicional.

Del ejemplo de la página de 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;
}

1

Su ejemplo básico sirve el mismo argumento que el de las constantes mismas. Por que usar

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

terminado

int arr[5];

Porque es mucho más fácil de mantener. Usar constexpr es mucho, mucho más rápido de escribir y leer que las técnicas de metaprogramación existentes.


0

Puede habilitar algunas nuevas optimizaciones. consttradicionalmente es una pista para el sistema de tipos, y no se puede utilizar para la optimización (por ejemplo, una constfunción miembro puede const_castmodificar el objeto de todos modos, legalmente, por constlo que no se puede confiar para la optimización).

constexprsignifica que la expresión es realmente constante, siempre que las entradas a la función sean constantes. Considerar:

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

Si esto se expone en algún otro módulo, el compilador no puede confiar en que GetNumber()no devolverá valores diferentes cada vez que se lo llame, incluso consecutivamente sin llamadas no constantes en el medio, porqueconst podría haberse descartado en la implementación. (Obviamente, cualquier programador que hizo esto debería ser fusilado, pero el lenguaje lo permite, por lo tanto, el compilador debe cumplir con las reglas).

Agregando constexpr:

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

El compilador ahora puede aplicar una optimización donde el valor de retorno de GetNumber()se almacena en caché y eliminar llamadas adicionales GetNumber(), porque constexpres una garantía más sólida de que el valor de retorno no cambiará.


En realidad const se puede usar en la optimización ... Es un comportamiento indefinido modificar un valor definido const incluso después de un const_castIIRC. Esperaría que fuera consistente para las constfunciones de los miembros, pero necesitaría verificar eso con el estándar. Esto significaría que el compilador puede hacer optimizaciones de forma segura allí.
Kos

1
@Warren: no importa si la optimización se realiza realmente, solo está permitida. @Kos: es una sutileza poco conocida que si el objeto original no se declaró const ( int xvs. const int x), entonces es seguro modificarlo const_castquitando const en un puntero / referencia a él. De const_castlo contrario, siempre invocaría un comportamiento indefinido y sería inútil :) En este caso, el compilador no tiene información sobre la constancia del objeto original, por lo que no puede decirlo.
AshleysBrain

@Kos No creo que const_cast sea el único problema aquí. El método const puede leer e incluso modificar una variable global. Por el contrario, alguien de otro hilo también podría modificar el objeto constante entre las llamadas.
enobayram

1
El "= 0" no es válido aquí y debe eliminarse. Lo haría yo mismo, pero no estoy seguro de que sea conforme con el protocolo SO.
KnowItAllWannabe

Ambos ejemplos no son válidos: el primero ( int GetNumber() const = 0;) debería declarar el GetNumber()método virtual. El segundo ( constexpr int GetNumber() const = 0;) no es válido porque el especificador puro ( = 0) implica que el método es virtual, pero constexpr no debe ser virtual (ref: en.cppreference.com/w/cpp/language/constexpr )
stj

-1

Cuando usar constexpr:

  1. siempre que haya una constante de tiempo de compilación.

Si bien estoy de acuerdo con usted, esta respuesta no explica por qué constexpr debería preferirse a las macros de preprocesador o const.
Sneftel

-3

Es útil para algo como

// constants:
const int MeaningOfLife = 42;

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

int some_arr[MeaningOfLife()];

Ate esto con una clase de rasgos o similar y se vuelve bastante útil.


44
En su ejemplo, ofrece cero ventajas sobre una constante simple, por lo que realmente no responde la pregunta.
JALF

Este es un ejemplo artificial, imagínese si MeaningOfLife () obtiene su valor de otro lado, digamos otra función o #define o series therof. Es posible que no sepa lo que devuelve, puede ser el código de la biblioteca. Otros ejemplos, imagine un contenedor inmutable que tiene un método constexpr size (). Ahora puede hacer int arr [container.size ()];
plivesey

2
@plivesey, ¿puedes editar tu respuesta con un mejor ejemplo?
Mukesh
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.