c++ gcc clang c++14 one-definition-rule

c++ - Comportamiento impar que pasa miembros constexpr estáticos sin definiciones por valor



llvm 3.7 0 (1)

Me sorprendió encontrar que GCC y Clang no están de acuerdo sobre si debo darme un error de vinculador al pasar un miembro constexpr estático por valor cuando no hay una definición fuera de clase:

#include <iostream> #include <type_traits> #include <typeinfo> template <typename X> void show(X) { std::cout << typeid(X).name() << std::endl; } template <typename T> struct Foo { //static constexpr struct E {} nested {}; // works in gcc and clang //static constexpr struct E {T x;} nested {}; // doesn''t work in gcc //static constexpr enum E {} nested {}; // works in gcc and clang //static constexpr enum E { FOO } nested {}; // works in gcc and clang //static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn''t work in gcc }; int main() { Foo<int> x; show(x.nested); }

Fragmento se puede jugar here .

Me gustaría usar la sintaxis de la primera línea sin una definición fuera de clase:

static constexpr struct E {} nested {}; // works in gcc and clang

Cuando no hay miembros en E , a Clang y GCC solo parece importarles que no tengo una definición de nested fuera de la clase si disparo la ODR (por ejemplo, tomando la dirección). ¿Es este estándar obligatorio o suerte?

Cuando hay miembros, GCC (5.2) parece querer además que yo haya definido manualmente un constructor de copia constexpr. ¿Es esto un error?

Desde Google y SO, he encontrado varias respuestas diferentes, pero es difícil separar cuáles están al día con C ++ 14. En C ++ 98/03, creo que solo los tipos enteros podrían inicializarse dentro de la clase. Creo que C ++ 14 expandió esto a tipos ''literales'' que incluyen cosas construibles constexpr. No sé si esto es lo mismo que decir que tengo permiso para salirme con la suya sin tener una definición fuera de clase.


Por lo tanto, en los casos en que E es una clase, todas parecen ser infracciones odr, si miramos la página odr-use en odr-use dice:

Informalmente, un objeto se usa de forma estándar si se toma su dirección, o si una referencia está vinculada a él, y una función se usa de forma estándar si se realiza una llamada a la función o se toma su dirección. Si un objeto o una función es odr-used, su definición debe existir en algún lugar del programa; una violación de eso es un error de tiempo de enlace.

y en este caso tomamos una referencia cuando llamamos al constructor de copia en esta línea:

show(x.nested);

También vale la pena tener en cuenta que las infracciones odr no requieren un diagnóstico .

Se ve lo que se ve en algunos casos en los efectos de la construcción del constructor con gcc si usamos -fno-elide-constructors obtenemos un error para todos los casos en los que E es una clase. En los casos de enumeración, se aplica la conversión de valor a valor y, por lo tanto, no se utiliza ODR.

Actualizar

dyp me indicó el informe de defecto 1741, que cuestiona si la vinculación al parámetro de referencia de un ctor de copia es o no un uso:

¿Odr-use T :: s, que requiere que tenga una definición, debido a que está vinculado al parámetro de referencia del constructor de copias de S?

y el resultado fue el siguiente cambio al [basic.def.odr] párrafo 3:

Una variable x cuyo nombre aparece como una expresión potencialmente evaluada ex es odr-used a menos que x cumpla con los requisitos para aparecer en una expresión constante (5.20 [expr.const]) aplicando la conversión de lvalue a rvalue (4.1 [conv.lval ]) a x produce una expresión constante (5.20 [expr.const]) que no invoca ninguna función no trivial y, si x es un objeto, ex es un elemento del conjunto de resultados potenciales de una expresión e, donde la conversión lvalue a rvalue (4.1 [conv.lval]) se aplica a e, o e es una expresión de valor descartado (Cláusula 5 [expr]). esto es odr-utilizado ...

Entonces la pregunta es qué casos están cubiertos por este cambio. Parece que estos ejemplos están bien:

//static constexpr struct E {} nested {}; // works in gcc and clang //static constexpr struct E {T x;} nested {}; // doesn''t work in gcc static constexpr struct E { constexpr E() {} constexpr E(const E&) = default; T x=T(); } nested {}; // doesn''t work in gcc

Dado que el ctor de copia es trivial mientras que este no es trivial:

//static constexpr struct E { constexpr E() {} constexpr E(const E&) {} T x=T();} nested {}; // works in gcc and clang

Podemos confirmar esto usando std::is_trivially_copyable , std::is_trivially_copyable en vivo .

Los casos de enumeración todavía están bien por las mismas razones que declaré originalmente.

El defecto también informa varianza en la implementación.