c++ language-lawyer c++17 stdoptional

c++ - clang 5: std:: tornillos de instanciación opcionales std:: is_constructible trait del tipo de parámetro



language-lawyer c++17 (2)

Esto parece un error de compilación. De [class]

Una clase se considera un tipo de objeto completamente definido (o tipo completo) en el cierre } del especificador de clase.

Lo que significa que la Victim está completa en std::optional<Victim> , por lo que no es diferente a ningún otro tipo en este contexto.

De [meta]

La condición de predicado para una especialización de plantilla is_constructible<T, Args...> cumplirá si y solo si la siguiente definición de variable estaría bien formada para alguna variable inventada t : T t(declval<Args>()...);

Que es la inicialización directa de t con argumentos de tipo Args... , o si sizeof...(Args) == 0 , es la inicialización de valores t .

En este caso, la inicialización de valor t es la inicialización predeterminada de t , que es válida, por lo tanto, std::is_constructible_v<Victim> debe ser verdadero.

Con todo lo dicho, los compiladores parecen estar luchando mucho compilando esto.

Se detectó un comportamiento realmente extraño e inesperado de clang 5 cuando se cambia a c ++ 17 y se reemplaza la solución std::optional con la estándar. Por alguna razón, emplace() estaba deshabilitado debido a una evaluación defectuosa de un rasgo std::is_constructible de la clase de parámetro.

Deben cumplirse algunas condiciones previas específicas antes de que se reproduzca:

#include <optional> /// Precondition #1: T must be a nested struct struct Foo { struct Victim { /// Precondition #2: T must have an aggregate-initializer /// for one of its members std::size_t value{0}; }; /// Precondition #3: std::optional<T> must be instantiated in this scope std::optional<Victim> victim; bool foo() { std::optional<Victim> foo; // An error foo.emplace(); /// Assertion is failed static_assert(std::is_constructible<Victim>::value); } };

Ejemplo vivo en godbolt.org

Cambia cualquiera de las condiciones previas y compila como se espera. ¿Existe alguna incoherencia desconocida en el estándar que haga que Clang rechace este código mientras cumple con las normas?

Como nota al margen: GCC 7.1 y GCC 7.2 no tienen ningún problema con el código anterior.

Informe de error en: bugs.llvm.org


Muy bien, desenterrado las citas relevantes. El quid de la cuestión es cómo std::is_constructible debe manejar Victim . La autoridad más concluyente es C ++ 17 (n4659). Primero [meta.unary.prop/8] :

La condición de predicado para una especialización de plantilla is_constructible<T, Args...> cumplirá si, y solo si, la siguiente definición de variable estaría bien formada para alguna variable inventada t:

T t(declval<Args>()...);

[Nota: estos tokens nunca se interpretan como una declaración de función . - nota final] La verificación de acceso se realiza como en un contexto no relacionado con T y cualquiera de los Args. Solo se considera la validez del contexto inmediato de la inicialización de la variable.

La nota que resalté no es normativa (por ser una nota), pero coincide con [temp.variadic]/7 :

... Cuando N es cero, la creación de instancias de la expansión produce una lista vacía. Tal creación de instancias no altera la interpretación sintáctica del constructo adjunto, incluso en los casos en que omitir la lista por completo estaría mal formado o daría como resultado una ambigüedad en la gramática.

Entonces, para los propósitos de is_constructible , este T t(); de hecho hace t una declaración variable. Esta inicialización es una inicialización de valor porque [dcl.init/11] dice lo siguiente:

Un objeto cuyo inicializador es un conjunto vacío de paréntesis, es decir, (), se inicializará con valores.

Eso significa que el rasgo termina comprobando si la Victim puede inicializarse con valores. Que se pueda. Es un agregado, pero el compilador todavía define un indicador predeterminado implícitamente predeterminado (para respaldar la inicialización del valor, obviamente).

Larga historia corta. Clang tiene un error, deberías reportarlo.