¿Legítimo para inicializar una matriz en un constructor constexpr?

11

¿Es legítimo el siguiente código?

template <int N>
class foo {
public:
    constexpr foo()
    {
        for (int i = 0; i < N; ++i) {
            v_[i] = i;
        }
    }

private:
    int v_[N];
};

constexpr foo<5> bar;

Clang lo acepta, pero GCC y MSVC lo rechazan.

El error de GCC es:

main.cpp:15:18: error: 'constexpr foo<N>::foo() [with int N = 5]' called in a constant expression
   15 | constexpr foo<5> bar;
      |                  ^~~
main.cpp:4:15: note: 'constexpr foo<N>::foo() [with int N = 5]' is not usable as a 'constexpr' function because:
    4 |     constexpr foo()
      |               ^~~
main.cpp:4:15: error: member 'foo<5>::v_' must be initialized by mem-initializer in 'constexpr' constructor
main.cpp:12:9: note: declared here
   12 |     int v_[N];
      |         ^~

Si este tipo de código estuviera bien, podría cortar bastantes usos de index_sequences.

Yongwei Wu
fuente
1
Gcc10 también lo acepta.
songyuanyao
¿podrías transcribir el error de MSVC?
max66
... y GCC también.
Evg
1
@songyuanyao - g ++ 10 lo acepta compilando C ++ 20; se niega a compilar C ++ 17 o anterior; el punto parece que _vdebería inicializarse en la lista de inicialización, hasta C ++ 17. Quizás haya cambiado algo en C ++ 20.
max66
2
@Evg Eso es realmente interesante, porque puede sugerir que Clang usa su "conciencia" de que un objeto de duración de almacenamiento estático se pone a cero para decir "está bien, este objeto puede haber sido inicializado por defecto pero las lecturas de su intmiembro nunca tendrán un comportamiento indefinido ". Me pregunto si GCC no hace eso es compatible, o al revés ...
ligereza corre en órbita el

Respuestas:

14

La inicialización trivial predeterminada estaba prohibida en un constexprcontexto hasta C ++ 20 .

Supongo que la razón es que es fácil leer "accidentalmente" de primitivas inicializadas por defecto, un acto que le da a su programa un comportamiento indefinido, y las expresiones con comportamiento indefinido están prohibidas constexpr( ref ). Sin embargo, el lenguaje se ha extendido de modo que ahora un compilador debe verificar si dicha lectura se lleva a cabo y, si no es así, se debe aceptar la inicialización predeterminada. Es un poco más de trabajo para el compilador, pero (¡como has visto!) Tiene beneficios sustanciales para el programador.

Este documento propone permitir la inicialización predeterminada para los tipos construibles trivialmente predeterminados en contextos constexpr sin dejar de permitir la invocación de comportamientos indefinidos. En resumen, mientras los valores no inicializados no se lean, tales estados deberían permitirse en constexpr en los escenarios asignados de pila y pila.

Desde C ++ 20, es legal dejar v_"sin inicializar" como lo ha hecho. Luego ha asignado todos los valores de sus elementos, lo cual es genial.

Carreras de ligereza en órbita
fuente
44
@ max66 ¡Yo también! Todo lo que hice fue escanear la lista de cambios de C ++ 20 en Wikipedia, encontrar algo relevante constexpry leer la propuesta vinculada;)
Lightness Races in Orbit
3
Lo malo es que hace más de 20 años que uso C ++. Si todos los días aprendo algo nuevo ... o soy un mal programador o C ++ se vuelve demasiado complicado.
max66
55
@ max66 Es casi seguro que es lo último. Además, el hecho de que sigue cambiando fundamentalmente cada dos años lo convierte en un objetivo de rápido movimiento. ¿Quién puede mantenerse al día con eso? Incluso los compiladores no se mantienen al día con eso.
Carreras de ligereza en órbita el
@ max66 Este documento me viene a la mente: ¡ Recuerda el Vasa!
Evg
@Evg Oh, wow, ese papel me había pasado por alto (IRONY). ¡Correcto!
Carreras de ligereza en órbita el