Referencia indefinida al carácter estático constexpr []

Resuelto Pubby asked hace 13 años • 6 respuestas

Quiero tener una static const charmatriz en mi clase. GCC se quejó y me dijo que debería usar constexpr, aunque ahora me dice que es una referencia indefinida. Si hago que la matriz no sea miembro, se compila. ¿Qué está pasando?

// .hpp
struct foo {
  void bar();
  static constexpr char baz[] = "quz";
};

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Pubby avatar Nov 05 '11 06:11 Pubby
Aceptado

Agregue a su archivo cpp:

constexpr char foo::baz[];

Motivo: debe proporcionar la definición del miembro estático, así como la declaración. La declaración y el inicializador van dentro de la definición de clase, pero la definición de miembro debe estar separada.

Kerrek SB avatar Nov 04 '2011 23:11 Kerrek SB

C++ 17 introduce variables en línea

C++ 17 soluciona este problema para constexpr staticlas variables miembro que requieren una definición fuera de línea si se usó odr. Consulte la segunda mitad de esta respuesta para obtener detalles anteriores a C++17.

La propuesta P0386 Variables en línea introduce la capacidad de aplicar el inlineespecificador a variables. En particular, este caso constexprimplica inlinevariables miembro estáticas. La propuesta dice:

El especificador en línea se puede aplicar tanto a variables como a funciones. Una variable declarada en línea tiene la misma semántica que una función declarada en línea: puede definirse, de manera idéntica, en múltiples unidades de traducción, debe definirse en cada unidad de traducción en la que se usa odr, y el comportamiento del programa es como si hay exactamente una variable.

y modificado [basic.def]p2:

Una declaración es una definición a menos que
...

  • declara un miembro de datos estáticos fuera de una definición de clase y la variable se definió dentro de la clase con el especificador constexpr (este uso está en desuso; consulte [depr.static_constexpr]),

...

y agregue [depr.static_constexpr] :

Para compatibilidad con estándares internacionales de C++ anteriores, un miembro de datos estáticos constexpr se puede volver a declarar de forma redundante fuera de la clase sin inicializador. Este uso está en desuso. [ Ejemplo:

struct A {
  static constexpr int n = 5;  // definition (declaration in C++ 2014)
};

constexpr int A::n;  // redundant declaration (definition in C++ 2014)

 - fin del ejemplo]


C++14 y anteriores

En C++03, solo se nos permitía proporcionar inicializadores en clase para integrales constantes o tipos de enumeración constantes ; en C++11, su uso constexprse extendió a tipos literales .

En C++11, no necesitamos proporcionar una definición de alcance de espacio de nombres para un constexprmiembro estático si no se usa odr , podemos ver esto en el borrador de la sección estándar de C++11 9.4.2 [class.static.data] que dice ( énfasis mío en el futuro ):

[...] Se puede declarar un miembro de datos estáticos de tipo literal en la definición de clase con el especificador constexpr; si es así, su declaración especificará un inicializador de llave o igual en el que cada cláusula de inicializador que sea una expresión de asignación es una expresión constante. [Nota: En ambos casos, el miembro puede aparecer en expresiones constantes. —Nota final] El miembro aún estará definido en un alcance de espacio de nombres si se usa odr (3.2) en el programa y la definición del alcance del espacio de nombres no contendrá un inicializador.

Entonces la pregunta es: ¿se baz usa odr aquí?

std::string str(baz); 

y la respuesta es , por lo que también requerimos una definición del alcance del espacio de nombres.

Entonces, ¿cómo determinamos si una variable se utiliza odr ? La redacción original de C++ 11 en la sección 3.2 [basic.def.odr] dice:

Una expresión se evalúa potencialmente a menos que sea un operando no evaluado (Cláusula 5) o una subexpresión del mismo. Una variable cuyo nombre aparece como una expresión potencialmente evaluada se usa odr a menos que sea un objeto que cumpla con los requisitos para aparecer en una expresión constante (5.19) y se aplique inmediatamente la conversión de valor a valor (4.1) .

Entonces bazproduce una expresión constante , pero la conversión de valor-a-valor no se aplica inmediatamente ya que no es aplicable debido a que bazes una matriz. Esto se trata en la sección 4.1 [conv.lval] que dice:

Un glvalue (3.10) de un tipo T que no es función ni matriz se puede convertir en un prvalue.53 [...]

Qué se aplica en la conversión de matriz a puntero .

Esta redacción de [basic.def.odr] se cambió debido al Informe de defectos 712 ya que algunos casos no estaban cubiertos por esta redacción, pero estos cambios no cambian los resultados de este caso.

Shafik Yaghmour avatar Mar 04 '2015 04:03 Shafik Yaghmour

Esto es realmente una falla en C++ 11: como otros han explicado, en C++ 11 una variable miembro estática de constexpr, a diferencia de cualquier otro tipo de variable global de constexpr, tiene un enlace externo, por lo que debe definirse explícitamente en alguna parte.

También vale la pena señalar que, en la práctica, a menudo puedes salirte con la tuya con variables miembro estáticas de constexpr sin definiciones al compilar con optimización, ya que pueden terminar integradas en todos los usos, pero si compilas sin optimización, a menudo tu programa no podrá vincularse. Esto hace que esto sea una trampa oculta muy común: su programa se compila bien con la optimización, pero tan pronto como desactiva la optimización (tal vez para depurar), no se vincula.

Sin embargo, hay buenas noticias: ¡esta falla se solucionó en C++ 17! Sin embargo, el enfoque es un poco complicado: en C++ 17, las variables miembro estáticas de constexpr están implícitamente en línea . Tener en línea aplicado a variables es un concepto nuevo en C++ 17, pero efectivamente significa que no necesitan una definición explícita en ninguna parte.

SethML avatar Oct 12 '2017 22:10 SethML

Mi solución alternativa para la vinculación externa de miembros estáticos es utilizar constexprcaptadores de miembros de referencia (lo que no genera el problema que @gnzlbg planteó como comentario a la respuesta de @deddebme).
Este modismo es importante para mí porque detesto tener varios archivos .cpp en mis proyectos y trato de limitar el número a uno, que consta únicamente de sy #includeuna main()función.

// foo.hpp
struct foo {
  static constexpr auto& baz() { return "quz"; }
};

// some.cpp

  auto sz = sizeof(foo::baz()); // sz == 4

  auto& foo_baz = foo::baz();  // note auto& not auto
  auto sz2 =  sizeof(foo_baz);    // 4
  auto name = typeid(foo_baz).name();  // something like 'char const[4]'
Josh Greifer avatar Aug 30 '2018 11:08 Josh Greifer

¿No es la solución más elegante cambiar el char[]en:

static constexpr char * baz = "quz";

De esta manera podemos tener la definición/declaración/inicializador en 1 línea de código.

deddebme avatar Apr 19 '2016 14:04 deddebme