c++ - constexpr inicializando miembro estático usando función estática
constexpr in c++ (4)
1) El ejemplo de Ilya debe ser un código no válido basado en el hecho de que la barra de miembro de datos estáticos constexpr se inicializa fuera de línea violando la siguiente declaración en el estándar:
9.4.2 [class.static.data] p3: ... Un miembro de datos estáticos de tipo literal puede declararse en la definición de clase con el especificador constexpr; de ser así, su declaración deberá especificar un inicializador de llave o igual en el que cada cláusula de inicializador que sea una expresión de asignación sea una expresión constante.
2) El código en la pregunta de MvG:
class C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = foo(sizeof(int));
};
es válido por lo que veo e intuitivamente uno esperaría que funcionara porque el miembro estático foo (int) se define por el procesamiento de tiempo de los inicios de barra (asumiendo el procesamiento descendente). Algunos hechos:
- Sin embargo, sí estoy de acuerdo con que la clase C1 no está completa en el momento de invocación de foo (basada en 9.2p2), pero la integridad o incompletitud de la clase C1 no dice nada sobre si foo se define en lo que respecta al estándar.
- Busqué el estándar para la definición de funciones de miembros pero no encontré nada.
- Entonces, la afirmación mencionada por Ben no se aplica aquí si mi lógica es válida:
una invocación de una función constexpr indefinida o un constructor constexpr indefinido fuera de la definición de una función constexpr o un constructor constexpr;
class C1
{
constexpr static int foo() { return bar; }
constexpr static int bar = foo();
};
parece inválido pero por diferentes razones y no simplemente porque se llama a foo en el inicializador de la barra . La lógica es la siguiente:
- foo () se invoca en el inicializador de la barra de constestación estática , por lo que tiene que ser una expresión constante (en 9.4.2 p3).
- dado que es una invocación de una función constexpr, se activa la sustitución de invocación de función (7.1.5 p5).
- No hay parámetros para la función, por lo que lo que queda es "convertir implícitamente la expresión devuelta resultante o braced-init-list al tipo de retorno de la función como si fuera una inicialización de copia". (7.1.5 p5)
- la expresión de retorno es solo barra , que es un valor l y se necesita la conversión lvalor a rvalue.
pero por la viñeta 9 en (5.19 p2) cuya barra no satisface porque todavía no se ha inicializado:
- una conversión de lvalue a rvalue (4.1) a menos que se aplique a:
- un glvalue del tipo integral o de enumeración que se refiere a un objeto const no volátil con una inicialización precedente, inicializado con una expresión constante.
- una conversión de lvalue a rvalue (4.1) a menos que se aplique a:
por lo tanto, la conversión de lvalue a rvalue de bar no produce una expresión constante que falle el requisito en (9.4.2 p3).
- entonces, mediante la viñeta 4 en (5.19 p2), la llamada a foo () no es una expresión constante:
una invocación de una función constexpr con argumentos que, cuando se sustituyen por la sustitución de invocación de función (7.1.5), no producen una expresión constante
Requisitos
Quiero un valor constexpr
(es decir, una constante en tiempo de compilación) calculado a partir de una función constexpr
. Y quiero que ambos alcancen el espacio de nombre de una clase, es decir, un método estático y un miembro estático de la clase.
Primer intento
Primero escribí esto la (a mí) manera obvia:
class C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = foo(sizeof(int));
};
g++-4.5.3 -std=gnu++0x
dice a eso:
error: ‘static int C1::foo(int)’ cannot appear in a constant-expression
error: a function call cannot appear in a constant-expression
g++-4.6.3 -std=gnu++0x
queja:
error: field initializer is not constant
Segundo intento
OK, pensé, tal vez tengo que sacar cosas del cuerpo de la clase. Entonces intenté lo siguiente:
class C2 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar;
};
constexpr int C2::bar = C2::foo(sizeof(int));
g++-4.5.3
compilará eso sin quejas. Desafortunadamente, mi otro código utiliza algunos bucles for
-based, así que tengo que tener al menos 4.6. Ahora que miro más de cerca la lista de soporte , parece que constexpr
requeriría 4.6 también. Y con g++-4.6.3
me sale
3:24: error: constexpr static data member ‘bar’ must have an initializer
5:19: error: redeclaration ‘C2::bar’ differs in ‘constexpr’
3:24: error: from previous declaration ‘C2::bar’
5:19: error: ‘C2::bar’ declared ‘constexpr’ outside its class
5:19: error: declaration of ‘const int C2::bar’ outside of class is not definition [-fpermissive]
Esto suena realmente extraño para mí. ¿Cómo las cosas "difieren en constexpr
" aquí? No tengo ganas de agregar -fpermissive
ya que prefiero que mi otro código sea revisado rigurosamente. Mover la implementación de foo
fuera del cuerpo de la clase no tuvo ningún efecto visible.
Respuestas esperadas
¿Alguien puede explicar lo que está pasando aquí? ¿Cómo puedo lograr lo que intento hacer? Me interesan principalmente las respuestas de los siguientes tipos:
- Una forma de hacer que esto funcione en gcc-4.6
- Una observación de que las últimas versiones de gcc pueden tratar una de las versiones correctamente
- Un puntero a la especificación según la cual al menos uno de mis constructos debería funcionar, de modo que pueda molestar a los desarrolladores de gcc para que realmente funcione.
- Información de que lo que quiero es imposible de acuerdo con las especificaciones, preferiblemente con algún insigt en cuanto a la razón de ser de esta restricción
También son bienvenidas otras respuestas útiles, pero quizás no se aceptarán tan fácilmente.
El estándar requiere (sección 9.4.2):
Un miembro de datos
static
de tipo literal se puede declarar en la definición de clase con el especificadorconstexpr
; de ser así, su declaración deberá especificar un inicializador de llave o igual en el que cada cláusula de inicializador que sea una expresión de asignación sea una expresión constante.
En su "segundo intento" y el código en la respuesta de Ilya, la declaración no tiene un inicializador de llave o igual .
Tu primer código es correcto Es desafortunado que gcc 4.6 no lo acepte, y no conozco ningún lugar para probar convenientemente 4.7.x (por ejemplo, ideone.com sigue atascado en gcc 4.5).
Esto no es posible, porque desafortunadamente el Estándar no permite inicializar un miembro de datos constexpr
estáticos en ningún contexto donde la clase esté completa. La regla especial para inicializadores de llaves o igual en 9.2p2 solo se aplica a los miembros de datos no estáticos , pero este es estático.
La razón más probable para esto es que constexpr
variables constexpr
deben estar disponibles como expresiones constantes en tiempo de compilación desde los cuerpos de las funciones miembro, por lo que los inicializadores variables están completamente definidos antes de los cuerpos funcionales, lo que significa que la función aún está incompleta (no definida ) en el contexto del inicializador, y luego entra en vigencia, haciendo que la expresión no sea una expresión constante:
una invocación de una función
constexpr
indefinida o un constructorconstexpr
indefinido fuera de la definición de una funciónconstexpr
o un constructorconstexpr
;
Considerar:
class C1
{
constexpr static int foo(int x) { return x + bar; }
constexpr static int bar = foo(sizeof(int));
};
Probablemente, el problema aquí está relacionado con el orden de declaración / definiciones en una clase. Como todos saben, puede usar cualquier miembro incluso antes de que se declare / defina en una clase.
Cuando define el valor de constexpr en la clase, el compilador no tiene la función constexpr disponible para usarse porque está dentro de la clase.
Quizás, la respuesta de Philip , relacionada con esta idea, sea un buen punto para entender la pregunta.
Tenga en cuenta este código que se compila sin problemas:
constexpr int fooext(int x) { return x + 1; }
struct C1 {
constexpr static int foo(int x) { return x + 1; }
constexpr static int bar = fooext(5);
};
constexpr static int barext = C1::foo(5);
#include <iostream>
class C1
{
public:
constexpr static int foo(constexpr int x)
{
return x + 1;
}
static constexpr int bar;
};
constexpr int C1::bar = C1::foo(sizeof(int));
int main()
{
std::cout << C1::bar << std::endl;
return 0;
}
Tal inicialización funciona bien, pero solo en clang