constexpr inicializando miembro estático usando función estática


Requisitos

Quiero un valor constexpr (es decir, una constante en tiempo de compilación) calculado a partir de una función constexpr. Y quiero ambos de estos ámbito al espacio de nombres de una clase, es decir, un método estático y un miembro estático de la clase.

Primer intento

Primero escribí esto de la manera obvia: {[19]]}

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 se queja:

error: field initializer is not constant

Segundo intento

OK, pensé, tal vez tengo que mover las cosas fuera de el cuerpo de clase. Así que probé 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 recopilaremos eso sin quejas. Desafortunadamente, mi otro código usa algunos bucles for basados en rangos, por lo que tengo que tener al menos 4.6. Ahora que miro más de cerca la lista de soporte , parece que constexpr también requeriría 4.6. Y con g++-4.6.3 consigo

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 me suena muy extraño. ¿Cómo "difieren las cosas en constexpr" aquí? No tengo ganas de agregar -fpermissive ya que prefiero que mi otro código sea riguroso comprobar. Mover la implementación foo fuera del cuerpo de la clase no tuvo ningún efecto visible.

Respuestas esperadas

¿Puede alguien 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 versiones posteriores de gcc pueden tratar con una de las versiones correctamente
  • Un puntero a la especificación según la cual al menos uno de mis las construcciones deberían 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 detrás de esta restricción

Otras respuestas útiles también son bienvenidas, pero quizás no sean aceptadas tan fácilmente.

Author: MvG, 2012-07-17

4 answers

La Norma 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 especificador constexpr; si es así, su declaración especificará un inicializador brace-or-equal-initializer en el que cada cláusula de inicializador que es una expresión de asignación es una expresión constante.

En su "segundo intento" y el código en la respuesta de Ilya, la declaración no tiene un brace-or-equal-initializer.

Tu primer código es correcto. Es lamentable que gcc 4.6 no lo acepte, y no conozco ningún lugar para probar convenientemente 4.7.x (e. g. ideone.com todavía está atascado en gcc 4.5).

Esto no es posible, porque desafortunadamente el Estándar impide inicializar un miembro de datos estático constexpr en cualquier contexto donde la clase esté completa. La regla especial para inicializadores de llaves o iguales en 9. 2p2 solo se aplica a miembros de datos no estáticos, pero este es estático.

La razón más probable para esto es que constexpr las variables tienen que estar disponibles como expresiones constantes en tiempo de compilación desde dentro de los cuerpos de las funciones miembro, por lo que los inicializadores de variables están completamente definidos antes de los cuerpos de la función which lo que significa que la función sigue incompleta (indefinida) en el contexto del inicializador, y luego esta regla se activa, haciendo que la expresión no sea una constante expresión:

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;

Considere:

class C1
{
  constexpr static int foo(int x) { return x + bar; }
  constexpr static int bar = foo(sizeof(int));
};
 16
Author: Ben Voigt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-17 21:33:08

1) El ejemplo de Ilya debe ser un código no válido basado en el hecho de que la barra de miembros de datos constexpr estática se inicializa fuera de línea violando la siguiente instrucción en el estándar:

9.4.2 [clase.estática.datos] p3: ... Un miembro de datos estático de tipo literal se puede declarar en la definición de clase con el especificador constexpr; si es así, su declaración especificará un inicializador de llave o igual en que cada inicializador-cláusula de que es una tarea de expresión es un 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 tiempo de procesamiento de bar comienza (asumiendo el procesamiento de arriba hacia abajo). Algunos hechos:

  • Estoy de acuerdo, sin embargo, que la clase C1 no está completa en el punto de invocación de foo (basado en 9.2p2) pero lo completo o incompleto de la clase C1 no dice nada acerca de si foo está definido en lo que respecta al estándar.
  • Busqué en el estándar la definición de las funciones miembro, pero no encontré nada.
  • Así que la declaració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 una función undefined constructor constexpr fuera de la definición de una función constexpr o un constructor constexpr;

3) El último ejemplo dado por Ben, simplificado:
class C1
{
  constexpr static int foo() { return bar; }
  constexpr static int bar = foo();
};

Parece inválido pero por diferentes razones y no simplemente porque foose llama en el inicializador de bar. La lógica es la siguiente:

  • foo () se llama en el inicializador de la barra de miembros constexpr estática, por lo que tiene que ser una expresión constante (por 9.4.2 p3).
  • dado que es una invocación de una función constexpr, la invocación de la función la sustitución (7.1.5 p5) entra en acción.
  • No hay parámetros para la función, por lo que lo que queda es "convertir implícitamente la expresión devuelta resultante o la lista de inicio entre corchetes al tipo de retorno de la función como si fuera por inicialización de copia."(7.1.5 p5)
  • la expresión de retorno es simplemente bar, que es un lvalue y se necesita la conversión lvalue a rvalue.
  • Pero por la viñeta 9 en (5.19 p2) que bar no satisface porque todavía no se ha inicializado:

    • una conversión lvalue a rvalue (4.1) a menos que se aplique a:
      • un glvalue de tipo integral o enumeración que se refiere a un objeto const no volátil con una inicialización anterior, inicializado con una expresión constante.
  • Por lo tanto, la conversión lvalue a rvalue de bar no produce una expresión constante que no cumpla con el requisito en (9.4.2 p3).

  • así que por bullet 4 in (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 son sustituidos por sustitución de invocación de función (7.1.5), no producen una expresión constante

 4
Author: user1574647,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-03 17:06:39
#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

 3
Author: Ilya Lavrenov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-07-17 12:36:17

Probablemente, el problema aquí está relacionado con el orden de la declaración/definiciones en una clase. Como todos saben, puede usar cualquier miembro incluso antes de que sea declarado / definido en una clase.

Cuando se define el valor de constexpr en la clase, el compilador no tiene la función constexpr disponible para ser utilizada porque está dentro de la clase.

Quizás, La respuesta de Philip, relacionada con esta idea, es un buen punto para entender la pregunta.

Tenga en cuenta este código que 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);
 1
Author: EFenix,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:02:45