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 inventadat
: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.