Referencia indefinida a static constexpr char []

186

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, entonces se compila. Que esta pasando?

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

// .cpp
void foo::bar() {
  std::string str(baz); // undefined reference to baz
}
Pubby
fuente
1
Solo una corazonada, ¿funciona si baz es int por ejemplo? ¿Entonces puedes acceder? También podría ser un error.
FailedDev
1
@Pubby: Pregunta: ¿En qué unidad de traducción se definirá? Respuesta: Todo lo que incluye el encabezado. Problema: Viola la regla de una definición. Excepción: las integrales constantes en tiempo de compilación se pueden "inicializar" en encabezados.
Mooing Duck el
Se compila bien como int@MooingDuck. Funciona bien como no miembro. ¿No violaría eso la regla también?
Pubby
@ Pubby8: ints tramposo. Como no miembro, eso no debería permitirse, a menos que las reglas cambien para C ++ 11 (posible)
Mooing Duck
Teniendo en cuenta los puntos de vista y los votos a favor, esta pregunta requirió una respuesta más detallada, que agregué a continuación.
Shafik Yaghmour

Respuestas:

188

Agregar a su archivo cpp:

constexpr char foo::baz[];

Motivo: debe proporcionar la definición del miembro estático y 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
fuente
70
Eso se ve raro ... ya que no parece proporcionar al compilador información que no tenía antes ...
vides
32
¡Se ve aún más extraño cuando tienes tu declaración de clase en el archivo .cpp! Inicializa el campo en la declaración de clase, pero aún necesita " declarar " el campo escribiendo constexpr char foo :: baz [] debajo de la clase. Parece que los programadores que usan constexpr pueden compilar sus programas siguiendo un consejo extraño: declararlo nuevamente.
Lukasz Czerwinski
55
@LukaszCzerwinski: La palabra que estás buscando es "definir".
Kerrek SB
55
Correcto, no hay información nueva: declarar el usodecltype(foo::baz) constexpr foo::baz;
no-un-usuario
66
¿Cómo se verá la expresión si foo es tentado? Gracias.
Hei
80

C ++ 17 introduce variables en línea

C ++ 17 corrige este problema para las constexpr staticvariables miembro que requieren una definición fuera de línea si se usaba 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 las variables. En particular para este caso constexprimplica inlinepara las variables miembro estáticas. La propuesta dice:

El especificador en línea se puede aplicar tanto a las variables como a las funciones. Una variable declarada en línea tiene la misma semántica que una función declarada en línea: se puede definir, de manera idéntica, en múltiples unidades de traducción, se debe definir 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ático 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 anteriores de C ++, un miembro de datos estáticos constexpr puede ser redeclarado 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)

 - ejemplo final]


C ++ 14 y anterior

En C ++ 03, solo se nos permitía proporcionar inicializadores en clase para integrales const o tipos de enumeración const , en C ++ 11 el uso constexprse extendió a los 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 ):

[...] Un miembro de datos estáticos de tipo literal se puede declarar en la definición de clase con el especificador constexpr; en caso afirmativo, 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 todavía se definirá en un ámbito de espacio de nombres si se utiliza odr-(3.2) en el programa y la definición del ámbito 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 necesitamos una definición del alcance del espacio de nombres.

Entonces, ¿cómo determinamos si una variable se usa 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 satisfaga los requisitos para aparecer en una expresión constante (5.19) y se aplique inmediatamente la conversión lvalue-to-rvalue (4.1) .

Por bazlo tanto , produce una expresión constante, pero la conversión lvalue-to-rvalue no se aplica inmediatamente ya que no es aplicable debido a que bazes una matriz. Esto está cubierto en la sección 4.1 [conv.lval] que dice:

Un valor de gl (3.10) de un tipo T sin función y sin matriz se puede convertir en un valor de valor.53 [...]

Lo que se aplica en la conversión de matriz a puntero .

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

Shafik Yaghmour
fuente
Entonces, ¿tenemos claro que no constexprtiene absolutamente nada que ver con eso? ( bazes una expresión constante de todos modos)
MM
@MattMcNabb bueno, se requiere constexpr si el miembro no es un integral or enumeration typepero, de lo contrario, sí, lo que importa es que es una expresión constante .
Shafik Yaghmour
En el primer párrafo, "ord-used" debería leerse como "odr-used", creo, pero nunca estoy seguro con C ++
Egor Pasko
37

Esto es realmente una falla en C ++ 11: como han explicado otros, en C ++ 11 una variable miembro estática constexpr, a diferencia de cualquier otro tipo de variable global 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 puede salirse con la suya con variables miembro estáticas constexpr sin definiciones al compilar con la optimización, ya que pueden terminar en línea en todos los usos, pero si compila sin optimización, a menudo su programa no podrá vincularse. Esto lo convierte en una trampa oculta muy común: su programa se compila bien con la optimización, pero tan pronto como apaga la optimización (quizás para la depuración), no se puede vincular.

Sin embargo, 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 constexpr están implícitamente en línea . La aplicación en línea a las variables es un concepto nuevo en C ++ 17, pero efectivamente significa que no necesitan una definición explícita en ninguna parte.

SethML
fuente
44
Para obtener información de C ++ 17. ¡Puede agregar esta información a la respuesta aceptada!
SR
5

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

static constexpr char * baz = "quz";

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

deddebme
fuente
9
con char[]puede usar sizeofpara obtener la longitud de la cadena en tiempo de compilación, con char *no puede (devolverá el ancho del tipo de puntero, 1 en este caso).
gnzlbg
2
Esto también genera advertencia si desea ser estricto con ISO C ++ 11.
Shital Shah
Vea mi respuesta que no muestra el sizeofproblema, y ​​puede usarse en soluciones de "solo encabezado"
Josh Greifer
4

Mi solución para el enlace externo de los miembros estáticos es usar constexprcaptadores de miembros de referencia (que no se encuentra con el problema que @gnzlbg planteó como un comentario a la respuesta de @deddebme).
Este modismo es importante para mí porque odio tener múltiples archivos .cpp en mis proyectos e intento limitar el número a uno, que consiste en nada más que #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
fuente
-1

En mi entorno, gcc vesion es 5.4.0. Agregar "-O2" puede corregir este error de compilación. Parece que gcc puede manejar este caso cuando solicita la optimización.

Haishan Zhou
fuente