constexpr .. alguna analogia que lo explique mas facil ?

Iniciado por digimikeh, 9 Junio 2019, 17:59 PM

0 Miembros y 1 Visitante están viendo este tema.

digimikeh

Hola!

"spam"
Por sugerencia de un usuario del foro me he comprado el libro "The C++ Programming Language 4th Edition - Bjarne Strouptup",
"/spam"  (perdón  :laugh:) entonoces he llegado a una parte que empieza a hablar sobre constexpr,  pero aun no entiendo bien su funcionamiento, es decir, es lógico que quiere decir que se pueden crear expresiones constantes, pero no le veo mayor diferencia con la palabra reservada const al menos por ahora....

Lo que llegue a comprender solamente es que no puedo asignarle valores no-constantes a una expresión constante.....  estoy seguro que es constexpr es algo muy muy útil...  alguna luz del para que sirve?

Gracias..
Dungeons & dragons;
dragons.Attack();

Loretz

Jé, siempre conviene ser precavido con las recomendaciones de uno de por aquí...


Constexpr
¿Para qué sirve?


Tomado de los que dice Stroustrup en la "Part 1 - Introductory Material, Section 2.2.3 - Constants":

const es una promesa que uno le hace al compilador, dice que uno no va a tratar de modificar lo que declaró const, y el compilador lo cree.

constexpr sirve para que la expresión se evalúe (se asigne, se calcule) cuando se está compilando, y no cuando se ejecuta el programa.

Se usa principalmente para especificar constantes, para permitir ubicar datos en memoria donde sea difícil que vayan a corromperse, y también por razones de rendimiento (performance).
... (etcétera) ...

En esa sección pone varios ejemplos, y me voy a tomar el atrevimiento de agregar un par de comentarios como llamadas al pie:

Citarconst int dmv = 17; // dmv is a named constant --> (1)
int var = 17; // var is not a constant
constexpr double max1 = 1.4*square(dmv); // OK if square(17) is a constant expression --> (2)
constexpr double max2 = 1.4*square(var); // error : var is not a constant expression
const double max3 = 1.4*square(var); // OK, may be evaluated at run time
double sum(const vector<double>&); // sum will not modify its argument
vector<double> v {1.2, 3.4, 4.5}; // v is not a constant
const double s1 = sum(v); // OK: evaluated at run time --> (3)
constexpr double s2 = sum(v); // error : sum(v) not constant expression

(1) Muy probablemente el compilador decida poner a dmv en un sector de memoria de sólo lectura, pero no está obligado.
(2) Dice que max1 podrá ser constexpr siempre que la función square() también lo sea.
(3) acá se declara a s1 como const, pero se va a calcular en el runtime (cuando se ejecuta el programa). La promesa es que una vez inicializado no se va a modificar.

¿Pero cómo es eso de hay funciones constexpr?
Más abajo lo aclara:
Para que una función pueda usarse en una contant expression tiene que estar definida como constexpr; ejemplo:

Citarconstexpr double square(double x) { return x*x; } // ajá!

quiere decir que si se le pasa un double declarado como constexpr, la función square también puede usarse en otras declaraciones constexpr.

¿Y en dónde está la gracia?
en que toda la expresión se va a evaluar (calcular) "at compile time", mientras se compila, así que va a tardar un poquito más el compilador pero después el ejecutable no va a tener que hacer nada; la llamada a función, la operación, el valor devuelto, la asignación, nada de eso va a ser necesario.



- Después, en la "Part 2 - Basic Facilities, Section 10.4 - Constant Expressions" se extinde un poco en este concepto. Pero después, ahora no hay apuro.


También, como referencia, siempre está "la documentación"
https://en.cppreference.com/w/cpp/language/constexpr


digimikeh

 >:D No se quien fue el responsable que me dio el dato del libro  :silbar:


De lo que he visto hasta ahora también se usa con las plantillas...
vamos a ver que tal la sección donde lo aclara con mas detalles...
y por cierto, gracias por la explicacion.
Dungeons & dragons;
dragons.Attack();

digimikeh

No se si me ha quedado mas o menos claro pero lo que acabo de captar es lo siguiente:

1) Las funciones declaradas como constexpr solo van a retornar literales constantes, ej:

Código (cpp) [Seleccionar]

     return (a + b);
     return 5 * 8;
     return (580 * a + (b / 2));



y nunca variables:

Código (cpp) [Seleccionar]

     int v = (a + b);
     return v; 

     float x = (580 * a + (b / 2));
     return x;


           

Vi otra cosa respecto a constexpr respecto a int vs double y float..

Código (cpp) [Seleccionar]


//esto no da problemas:
const int ix = 7;
constexpr double dx = 520.5 * ix;

//pero esto si los da:
const float fx = 7;
constexpr double dx = 520.5 * fx;

//esto tambien da problemas:
const double dx1 = 7;
constexpr double dx2 = 520.5 * dx1;



El compilador dice que ni const float fx, ni const double dx1 son utilizables como constexpr.  en cambio estoy forzado a lo siguiente para que funcione:

Código (cpp) [Seleccionar]


constexpr float fx = 7.5f;                 
constexpr double dx = 520.5 * fx;



Es decir, en los casos de los tipos double y float, no debo usar const, sino constexpr..

La gran pregunta, por que ?

Salud! y gracias.



 
Dungeons & dragons;
dragons.Attack();

Loretz

CitarVi otra cosa respecto a constexpr respecto a int vs double y float..

Está bien eh.

No tengo una cita del estándar que sirva de documento, pero creo que...

const int tiene un estátus especial que no tienen los demás.

En el primer caso, por qué esto no da problemas?
//esto no da problemas:
const int ix = 7;
constexpr double dx = 520.5 * ix;


Desde antes de que se inventara constexpr (antes del C++11) un const int siempre tuvo un valor constante conocido en tiempo de compilación (siempre fue una "const expression"); por ejemplo cuando se usa como dimensión de un array:
    const int ix = 7;
    int array_ix[ix];


-- Nota: algunos compiladores (gcc, por ejemplo) incluyen una extensión al lenguaje que permite usar un int no constante como dimensión de un array ("variable length arrays"), pero es una extensión particular, fuera del estándar.

Pero no sucede lo mismo con floats o doubles, al menos no cuando se compila con el gcc. ¿Por qué? No sé. No lo sé, y sospecho que la respuesta no debe ser de las más simples, sobre todo porque los distintos compiladores tampoco se han puesto de acuerdo en esto.

Puse como ejemplo una versión más simple en Compiler explorer ( https://godbolt.org/z/sQ9BHB )

int main()
{
    //esto no da problemas:
    const int ix = 7;
    constexpr int ci = ix;

    //pero esto si los da:
    const float fx = 7.0;
    constexpr float cf = fx;

    //esto tambien da problemas:
    const double dx = 7;
    constexpr double cd = dx;

}



  • Para gcc está mal (puedes ver la lista de errores en el Compiler Explorer)
  • Para clang también está mal.
  • Para icc está bien.
  • para msvc también está bien.

Bueno, si alguien sabe algo más, que nos cuente.


digimikeh

aha... veo que son caprichos del comitee  >:(

Lo que acabas de decir sobre int (como un constante por defecto) me hace bastante sentido quizá por eso la exclusión..

Bueno, y que opinas de lo que pienso sobre las funciones que retornan un tipo que tiene constexpr ?..

Gracias.
Dungeons & dragons;
dragons.Attack();

RayR

#6
No es tanto que int sea especial, sino que en realidad los flotantes son el "problema". De hecho, todo esto no es específico de constexpr. Siempre ha sido limitado lo que se puede hacer con este tipo de datos. En general, no se pueden usar donde se necesiten expresiones constantes (conocidas en tiempo de compilación). Por ejemplo, no es posible usar flotantes como parámetros sin tipo de templates. Tampoco se pueden usar en static assertions, que funcionan perfectamente con const int, char, short, etc., pero no flotantes. Pero en ambos casos podemos usar flotantes constexpr. Si algunos compiladores permiten hacer algunas de estas cosas, simplemente están siendo demasiado permisivos, pero no es estándar.

La razón exacta por la que los flotantes están tan limitados en C++ no la sé, pero es casi seguro que (al menos una de las razones) sea por las diferencias en la representación de este tipo de datos. A lo largo de los años ha habido varios formatos distintos y cada arquitectura usaba el que quería, por lo que no había un único estándar. Las especificaciones de C y C++ siempre dejaron al criterio de cada compilador usar el que quisieran. Permitir el uso de flotantes en expresiones verdaderamente constantes habría dado lugar a posibles problemas, sobre todo por la compilación cruzada. Esto se refiere a la posibilidad de usar, por ejemplo, un compilador en Windows, corriendo sobre arquitectura x86/x86-64 (Intel y AMD), para generar binarios/ejecutables de una arquitectura o sistema operativo diferente (por ejemplo, Linux sobre ARM). Dado que las expresiones constantes se generan en tiempo de compilación, es posible que el valor calculado sea distinto a lo que se calcularía durante la ejecución del programa (ya que el ejecutable generado estaría funcionando en un sistema operativo o procesador distinto a aquél en el que se ejecutaba el compilador que lo generó). De hecho, sobre los constexpr el estándar dice justamente esto, que para flotantes no se puede garantizar que los valores generados en la compilación sean iguales a los que se calcularían en la ejecución. Esto podría darnos problemas si mezcláramos floats const y no const.

En realidad hoy ya no es tanto problema, ya que casi todo el mundo ha adoptado el estándar IEEE 754 para flotantes, así que veo poco probable que haya muchas diferencias, aún usando compilación cruzada. ¿Por qué no decidieron entonces hacer más flexibles también las restricciones de const float/double? Supongo que por compatibilidad con estándares anteriores y para dejar bien clara la distinción. Es decir, quien use flotantes constexpr, en teoría lo hará siendo consciente de la posibilidad de discrepancias anteriormente mencionada (el estándar es muy claro en eso, y me parece que también deberían serlo los autores de libros).

Citar1) Las funciones declaradas como constexpr solo van a retornar literales constantes, ej:
y nunca variables:

No. De hecho, sí pueden perfectamente devolver variables. Esto funciona correctamente (C++14 en adelante):

Código (cpp) [Seleccionar]
constexpr int cuadrado(int n) {
   int x = n * n;
   return x;
}

...

constexpr int numero = cuadrado(9);

// Esto tambien es valido. Ver explicacion en ultimo parrafo
int x;
...
int variable = cuadrado(x);
const int constante = cuadrado(x);

// Pero, naturalmente, no esto
constexpr int erroneo = cuadrado(x);


Las reglas de las funciones constexpr son algo complicadas, pero básicamente lo que necesitan es poder calcular el valor de retorno en tiempo de compilación.* Dado que en la línea 8 le estamos mandando un valor constante, 9 (también podríamos mandarle un const int debidamente inicializado), se cumple la condición.

*Eso no es del todo cierto. Como en las ultimas lineas del código de arriba, se le puede enviar variables como parámetros a una función constexpr, y hacer que no se pueda calcular en la compilación, pero entonces no podemos usar su resultado en lugares donde se espere una expresión constante.

digimikeh

#7
Que curioso, hice varias pruebas con funciones usando constexpr retornando una variable y siempre me marco error, pero si las cambiaba por literales, entonces funcionaban...


Y ahora que intento recrear la escena, funcionan retornando una variable..

:-\
Una consulta respecto al ultimo trozo de código...
En la línea 11, tienes int x;, pero no esta inicializado, que valor se le pasara a la función ?

saludos y gracias.




Ah!!.. ya vi el por que, pasa que en C++11 las funciones constexpr solo pueden retornar valores (o expresiones) constantes, no variables...

Sin embargo, como bien dices, la versión C++14 si lo puede hacer... debio ser un update del lenguaje...  es que el libro que estoy siguiendo es la versión 11..y precisamente esta usando la version 11 del compilador.

Ya me tenia sin cabello la duda...
Saludos..
Dungeons & dragons;
dragons.Attack();

Loretz

Disculpas si me paso de consejista ("consejista": opinólogo aconsejador), pero creo que no conviene mezclar demasiados temas a la vez. Si estás viendo el concepto de "constant expressions" creo que conviene concentrarse en qué es const y qué es constexpr, sus definiciones, significados, ejemplos de uso, pero no mucho más, y dejar (con pesar, claro), los detalles más finos para más adelante.

Porque como vemos, se nos mezclan temas más complejos que en lugar de ayudar a aclara las cosas las oscurecen bastante. Ejemplo: no sólo están los conceptos complejos (nada simples) que concurren en cualquier tema que se mire, además si seguimos todos los links que encontremos no vamos a perder como Hansel y Gretel. Y encima hasta tenemos la suerte de encontrarnos con que algunos compiladores de comportan de una manera y otros de otra.

[Nota]Este último punto, eso de que algunos compiladores se comportan de una manera y otros de otra, suele suceder cuando hacemos algo muy mal y el compilador tiene permiso para hacer lo que quiera con eso. En el estándar se denomina "Undefinded Behavior", comportamiento indefinido. Pero ¿cómo es que con algo aparentemente tan simple podemos hacer las cosas tan mal? Porque no es cierto que sea simple, acá no hay nada simple, lo simple ya se acabó hace mucho. Los temas se presenta más o menos simples para que podamos empezar a hacer algo útil sin asustarnos, pero no es cierto que sean simples, cualquier cosa, la más inocentes de las oraciones, esconde un mundo bastante complejo, como en la vida.[/Nota]

Pero volviendo a lo nuestro...

CitarEn la línea 11, tienes int x;, pero no esta inicializado, que valor se le pasara a la función ?

Yo sé la respuesta, pero como la pregunta va dirigida a RayR...

CitarAh!!.. ya vi el por que, pasa que en C++11 las funciones constexpr solo pueden retornar valores (o expresiones) constantes, no variables...

Las funciones no devuelven variables; las funciones devuelven valores, "if any". https://en.cppreference.com/w/cpp/language/return

En el caso de funciones declaradas constexpr, en el estándar C++11 sólo se permitía que el cuerpo de la función tuviera una sola línea con el return. En C++14 se permite más de una línea. Ejemplo:

Código (cpp) [Seleccionar]
constexpr int fun_cpp14(int a) // C++14 permite dos o mas lineas; siempre devuelve un valor.
{
    int i = a + 5;
    return i;
}

constexpr int fun_cpp11(int a) // C++11 solo permite una linea con el "return"
{
    return a + 5;
}


Funcionalmente no ha cambiado nada, es un poco más cómodo nada más. Para más detalles puedes consultar en "la documentación" [Copy elision] https://en.cppreference.com/w/cpp/language/copy_elision

Pero, fíjate un poco en lo que decía más arriba, ¿hasta dónde conviene fijarse en los detalles? O mejor dicho, ¿es ahora que me conviene meterme con esos detalles? Para entender ese artículo se necesita también entender qué es un prvalue (value semantics), y tener muy claro eso de los copy y move constructors, por supuesto, pero por suerte aclara que esos constructores no necesitan estar presentes ni accesibles. Buena aclaración, porque eso es así a partir del C++17... Y así todo. Por eso como consejólogo profesional, yo te diría que conviene ser metódico y paciente; cada oración tiene una densidad de plomo. Aquí ya no quedan inocentes, se los han comido a todos.


RayR

#9
CitarUna consulta respecto al ultimo trozo de código...
En la línea 11, tienes int x;, pero no esta inicializado, que valor se le pasara a la función ?

Basura. Y Es incorrecto intentar acceder al valor de una variable no inicializada, pero fíjate que puse unos puntos suspensivos luego de esa declaración. Eso para dar a entender que hay más código intermedio donde, presumiblemente, se le da un valor a x. No quise inicializarla ahí, pues podría dar la impresión errónea de que ese código sólo funciona si le damos un valor inicial y literal a la variable.

Y coincido en que cuando empiezas es mejor no intentar profundizar demasiado. Las reglas de C++ se han hecho demasiado complicadas. Sin ir más lejos, el especificador constexpr probablemente ni siquiera tendría razón de aparecer en un manual introductorio, pero clar, el de Stroustrup no es un texto básico. Aunque tampoco te sientas mal por haberlo comprado; si bien no es recomendable para alguien que apenas empieza, sí que es un muy buen libro y en algún punto te puede servir bastante.

Dicho lo anterior, si de pronto tienes dudas que simplemente no te puedes quitar de la mente (a veces pasa), puedes preguntar y, si ves que las respuestas son complicadas y no las entiendes aún, guardarlas para más adelante. Yo en su momento las almacenaba en un documento (por si después ya no recordaba dónde pregunté, o ya no existía la página) y con eso me quitaba la "comezón", pues sabía que mi respuesta estaba ahí, esperando para cuando tuviera los conocimientos necesarios.