Diferencia entre constexpr y la variable global estática constexpr

6 minutos de lectura

En el estándar C++11, ¿cuál es la diferencia entre constexpr y static constexpr variables globales cuando se definen en un encabezado? Más específicamente, cuando varias unidades de traducción incluyen el mismo encabezado, ¿qué declaración (si la hay) garantiza definir la misma variable en todas las unidades de traducción?

p.ej,

cexpr.h:

#ifndef CEXPR_H
#define CEXPR_H

constexpr int cint = 1;
static constexpr int scint = 1;

#endif

a.cpp:

#include "cexpr.h"

b.cpp:

#include "cexpr.h"

  • No hay diferencia. constexpr implica const. const implica static.

    – aprendiz

    31 de agosto de 2017 a las 18:04

  • Ninguna: constexpr en variables implica consty las variables integrales const en el ámbito del espacio de nombres tienen un vínculo interno de forma predeterminada.

    – KerrekSB

    31 de agosto de 2017 a las 18:04

  • @cpplearner: Nit: no diría exactamente “const implica estática”, porque extern const int es válido, pero extern static const int no es — así que el static no es tanto “implícito” como una especie de “predeterminado”.

    – KerrekSB

    31 de agosto de 2017 a las 18:05

  • @KerrekSB Para asegurarme de que entiendo, tampoco constexpr ni static constexpr ¿Me permitiría obtener el mismo objeto en unidades de traducción separadas?

    – Danra

    31 de agosto de 2017 a las 18:08


  • @Danra: en C++ 14, no, tendrías que usar extern const int en su lugar (que todavía es adecuado como una expresión constante en la TU que lo define, ya que necesita un inicializador para poder utilizarlo como una expresión constante). En C++17 usarías inline constexpr int a = 10; para obtener un solo objeto.

    – KerrekSB

    31 de agosto de 2017 a las 18:14


En su ejemplo actual no hay diferencia: en las declaraciones de variables, constexpr implica consty una variable const en el ámbito del espacio de nombres tiene un vínculo interno de forma predeterminada (por lo que agregar static no cambia nada).

En C++14, no puede declarar una variable como constexpr y haga que tenga un enlace externo a menos que solo haga esto en una sola unidad de traducción. La razón es que constexpr las variables requieren un inicializador, y una declaración con inicializador es una definición, y solo debe tener una única definición.

Sin embargo, lo que Ud. pueden hacer es usar una constante integral normal, que puede declarar (no definir) como externy en la unidad de traducción donde se define puede incluso usarse como una expresión constante:

lib.h:

extern const int a;

lib.cpp:

#include "lib.h"

const int a = 10;

int b[a] = {1, 2, 3};   // OK in this translation unit

En C ++ 17, hay una nueva característica “variables en línea” que le permite decir:

inline constexpr int a = 10;

Y esta es una “definición en línea” que puede aparecer repetidamente, y cada definición define el mismo entidad (al igual que todas las demás entidades “en línea” en el idioma).

  • Es posible tener un constexpr definición en un archivo de encabezado antes de C++17, pero tenía que usar varias soluciones como tener un inline constexpr función con un constexpr static variable en su interior.

    – Daniel H.

    31 de agosto de 2017 a las 21:13

  • @DanielH: Sí, posiblemente, siempre que no use la variable.

    – KerrekSB

    31 de agosto de 2017 a las 21:29

  • Lo que quise decir fue, en un archivo de encabezado, inline constexpr int i() {static constexpr int value = 17; return value;} debería funcionar, y luego puedes decir i(). Es raro usar la sintaxis de llamadas a funciones, pero creo que funciona.

    – Daniel H.

    31 de agosto de 2017 a las 21:43


  • @DanielH: No, lo siento, tienes razón, ese está bien. Tengo esto confundido. Un ejemplo que no estaría bien es static constexpr int a = 10; inline constexpr const int& f() { return a; }.

    – KerrekSB

    31/08/2017 a las 23:30

  • @KerrekSB Sí, necesitas el static variable a ser en el interior la función. Tampoco estaría bien la mayoría de los intentos de deshacerse de la sintaxis de llamada de función, como inline constexpr const Foo& f() { static constexpr Foo x; return x; } constexpr Foo& x = f();, desafortunadamente. creo que usted pueden hágalo si puede dejar que sea un miembro de la clase, aunque no recuerdo cómo e implica una redirección complicada.

    – Daniel H.

    31 de agosto de 2017 a las 23:34

Avatar de usuario de IclodQ
IclodQ

Creo que este artículo lo explica más claro. 6.8 — Constantes globales y variables en línea

Debido a que las constantes globales tienen vínculos internos, cada archivo .cpp obtiene una versión independiente de la variable global que el vinculador no puede ver. En la mayoría de los casos, debido a que son constantes, el compilador simplemente optimizará las variables.
El término “optimizar lejos” se refiere a cualquier proceso en el que el compilador optimiza el rendimiento de su programa eliminando cosas de una manera que no afecta la salida de su programa. Por ejemplo, supongamos que tiene una variable constante x que se inicializa en el valor 4. Dondequiera que su código haga referencia a la variable x, el compilador puede simplemente reemplazar x con 4 (dado que x es constante, sabemos que nunca cambiará a un valor diferente ) y evita tener que crear e inicializar una variable por completo.

Entonces, “cint” y “scint” son todas variables de vinculación interna.

La mejor práctica para definir una variable global después de C++ 17:

inline constexpr double pi = 0;

Mecanismo de trabajo:

C++17 introdujo un nuevo concepto llamado variables en línea. En C++, el término en línea ha evolucionado para significar “se permiten múltiples definiciones”. Por lo tanto, una variable en línea es aquella que se puede definir en múltiples archivos sin violar la regla de definición única. Las variables globales en línea tienen enlaces externos de forma predeterminada.

Las variables en línea tienen dos restricciones principales que deben obedecerse: 1) Todas las definiciones de la variable en línea deben ser idénticas (de lo contrario, se producirá un comportamiento indefinido). 2) La definición de variable en línea (no una declaración directa) debe estar presente en cualquier archivo que use la variable.

El compilador consolidará todas las definiciones en línea en una sola definición de variable. Esto nos permite definir variables en un archivo de encabezado y tratarlas como si solo hubiera una definición en un archivo .cpp en alguna parte. Estas variables también conservan su constexpr-ness en todos los archivos en los que se incluyen.

Si puedes, prefiere el static constexpr porque con el constexpr depende de la cadena de herramientas la probabilidad de que se haga en tiempo de compilación. Gcc es más agresivo, MSVS menos agresivo y clang está en el medio.

En lugar de dejar que el optimizador decida algunos valores, lo hará en tiempo de compilación, sea más explícito y lo fuerce.

Referencia:

https://www.youtube.com/watch?v=4pKtPWcl1Go

  • Gracias, probablemente un buen consejo a seguir en general para evitar confusiones, aunque para las variables globales no hace ninguna diferencia (@Kerrek SB explica por qué en el primer párrafo de su respuesta).

    – Danra

    4 de junio a las 0:41

¿Ha sido útil esta solución?