constexpr - Variables y objetos

constexpr - Variables y objetos

Si declara una variable como constexpr, el compilador las evaluará en el momento de la compilación. Esto no solo se aplica a los tipos integrados, sino también a las instancias de tipos definidos por el usuario. Existen algunas restricciones serias para que los objetos los evalúen en tiempo de compilación.

Para hacerlo más fácil, usaré tipos incorporados como bool, char, int y double. Llamaré a los tipos de datos restantes tipos de datos definidos por el usuario. Estos son, por ejemplo, std::string, tipos de la biblioteca C++ y tipos de datos definidos por el usuario. Los tipos definidos por el usuario suelen contener tipos integrados.

Variables

Al usar la palabra clave constexpr, la variable se convierte en una expresión constante.

constexpr double myDouble= 5.2;

Por lo tanto, puedo usar la variable en contextos que requieren una expresión constante. Por ejemplo, si quiero definir el tamaño de una matriz. Esto tiene que hacerse en tiempo de compilación.

Para la declaración de la variable constexpr, debe tener en cuenta algunas reglas.

La variable

  • es implícitamente const.
  • tiene que ser inicializado.
  • requiere una expresión constante para la inicialización.

La regla tiene sentido. Si evalúo una variable en tiempo de compilación, la variable solo puede depender de los valores que se pueden evaluar en tiempo de compilación.

Los objetos son creados por la invocación del constructor. El constructor tiene algunas reglas especiales.

Tipos definidos por el usuario

La clase MyDistance de la publicación Expresiones constantes con constexpr cumple con todos los requisitos para ser inicializada en tiempo de compilación. Pero, ¿cuáles son los requisitos?

Un constructor constexpr solo se puede invocar con expresiones constantes.

  1. no se puede usar el manejo de excepciones.
  2. debe declararse como predeterminado o eliminar o el cuerpo de la función debe estar vacío (C++11).

El tipo definido por el usuario constexpr

  1. no puede tener clases base virtuales.
  2. requiere que cada objeto base y cada miembro no estático se inicialicen en la lista de inicialización del constructor o directamente en el cuerpo de la clase. En consecuencia, sostiene que cada constructor utilizado (por ejemplo, de una clase base) debe ser un constructor constexpr y que los inicializadores aplicados deben ser expresiones constantes.

Lo sentimos, pero los detalles son aún más difíciles:cppreference.com. Para que la teoría sea obvia, defino la clase MyInt. MyInt muestra los puntos recién mencionados. La clase tiene además métodos constexpr. Hay reglas especiales para los métodos y funciones constexpr. Estas reglas seguirán en la próxima publicación, por lo que podemos concentrarnos en esta publicación en lo esencial sobre variables y tipos definidos por el usuario.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
// userdefinedTypes.cpp

#include <iostream>
#include <ostream>

class MyInt{
public:
 constexpr MyInt()= default;
 constexpr MyInt(int fir, int sec): myVal1(fir), myVal2(sec){}
 MyInt(int i){
 myVal1= i-2;
 myVal2= i+3;
 }
 
 constexpr MyInt(const MyInt& oth)= default;
 constexpr MyInt(MyInt&& oth)= delete;
 
 constexpr int getSum(){ return myVal1+myVal2; }
 
 friend std::ostream& operator<< (std::ostream &out, const MyInt& myInt){
 out << "(" << myInt.myVal1 << "," << myInt.myVal2 << ")"; 
 return out;
 }

private:
 int myVal1= 1998;
 int myVal2= 2003;

};

int main(){
 
 std::cout << std::endl;
 
 constexpr MyInt myIntConst1;
 MyInt myInt2;
 
 constexpr int sec= 2014;
 constexpr MyInt myIntConst3(2011,sec);
 std::cout << "myIntConst3.getSum(): " << myIntConst3.getSum() << std::endl;
 
 std::cout << std::endl;
 
 int a= 1998;
 int b= 2003;
 MyInt myInt4(a,b);
 std::cout << "myInt4.getSum(): " << myInt4.getSum() << std::endl;
 std::cout << myInt4 << std::endl;
 
 std::cout << std::endl;
 
 // constexpr MyInt myIntConst5(2000); ERROR
 MyInt myInt6(2000);
 std::cout << "myInt6.getSum(): " << myInt4.getSum() << std::endl;
 std::cout << myInt6 << std::endl;
 
 // constexpr MyInt myInt7(myInt4); ERROR
 constexpr MyInt myInt8(myIntConst3);
 
 std::cout << std::endl;
 
 int arr[myIntConst3.getSum()];
 static_assert( myIntConst3.getSum() == 4025, "2011 + 2014 should be 4025" );
 
}

La clase MyInt tiene tres constructores. Un constructor predeterminado de constexpr (línea 8) y un constructor que toma dos (línea 9) y toma un argumento (línea 10). El constructor con dos argumentos es un constructor constexpr. Por lo tanto, su cuerpo está vacío. Esto no es cierto para el constructor no constexpr con un argumento. La definición continúa con un constructor de copia predeterminado (línea 15) y un constructor de movimiento eliminado (línea 16). Además, la clase tiene dos métodos, pero solo el método getSum es una expresión const. Solo puedo definir las variables myVal1 y myVal2 (líneas 26 y 27) de dos maneras si quiero usarlas en objetos constexpr. Al principio, puedo inicializarlos en la lista de inicialización del constructor (línea 9); segundo, puedo inicializarlos en el cuerpo de la clase (líneas 26 y 27). La inicialización en la lista de inicialización del constructor tiene una prioridad más alta. No está permitido definir ambas variables en el cuerpo del constructor (líneas 11 y 12).

Para poner la teoría en práctica, aquí está el resultado del programa.

El programa muestra algunos puntos especiales:

  • Puede usar un constructor constexpr en tiempo de ejecución. Por supuesto, la instancia no es una expresión constante (línea 36 y línea 46).
  • Si declara una expresión no constante como constexpr, obtendrá un error de compilación (líneas 52 y 57).
  • Los constructores constexpr pueden coexistir con constructores que no son constexpr. Lo mismo se aplica a los métodos de una clase.

La observación clave es:Un objeto constexpr solo puede usar métodos constexpr.

Pero detente. ¿Cuál es la historia de las dos últimas líneas 62 y 63 en la función principal?

La prueba

Bastante sencillo. Son la doble prueba de que la llamada myIntConst3.getSum() se realiza en tiempo de compilación.

Al principio, C++ requiere que el tamaño de una matriz sea una expresión constante. En segundo lugar, static_assert evalúa su expresión en tiempo de compilación. De lo contrario, static_assert no se compilará.

Si reemplazo la línea 63

static_assert( myIntConst3.getSum() == 4025, "2011 + 2014 should be 4025" );

con la línea

static_assert( myIntConst4.getSum() == 4001, "1998 + 2003 should be 4001" );

, obtendré un error de compilación.

¿Qué sigue?

Creo que ya lo sabes. En la próxima publicación, escribiré sobre las funciones contextpr. Tienen con C++11 muchas restricciones que casi desaparecerán con C++14. Las funciones constexpr en C++14 se sienten casi como funciones normales. Por supuesto, mis puntos sobre las funciones también serán válidos para los métodos de las clases.