c++ - El acceso al tipo de miembro con `if constexpr` dentro de lambda genérica requiere que ambas ramas estén bien formadas: gcc vs clang
language-lawyer c++17 (1)
Considere dos struct
con diferentes alias de tipo de miembro:
struct foo { using x = int; };
struct bar { using y = float; };
Dada una T
en un contexto de template
, quiero obtener T::x
o T::y
dependiendo de lo que T
es:
template <typename T>
auto s()
{
auto l = [](auto p)
{
if constexpr(p) { return typename T::x{}; }
else { return typename T::y{}; }
};
return l(std::is_same<T, foo>{});
}
int main()
{
s<foo>();
}
g++
compila el código anterior, mientras que clang++
produce este error:
error: no type named ''y'' in ''foo''
else { return typename T::y{}; }
~~~~~~~~~~~~^
note: in instantiation of function template specialization ''s<foo>'' requested here
s<foo>();
^
en godbolt.org , con visor de conformidad.
¿ clang++
rechaza incorrectamente este código?
Tenga en cuenta que clang++
acepta el código al eliminar la indirección a través del lambda l
genérico:
template <typename T>
auto s()
{
if constexpr(std::is_same<T, foo>{}) { return typename T::x{}; }
else { return typename T::y{}; }
}
Vea la publicación de Richard Smith en la discusión estándar:
En la implementación que estoy familiarizado con [es decir, Clang], un problema clave es que los ámbitos léxicos utilizados al procesar una definición de función son fundamentalmente transitorios, lo que significa que retrasar la creación de instancias de alguna parte de la definición de plantilla de función es difícil de soportar. Las lambdas genéricas no sufren un problema aquí, porque el cuerpo de la lambda genérica se crea una instancia con la plantilla de función adjunta, [..]
Es decir, los cuerpos de los lambdas genéricos están parcialmente instanciados utilizando el contexto local (incluidos los argumentos de la plantilla) cuando se crea una instancia de la plantilla; por lo tanto, bajo la implementación de Clang, T::x
y T::y
se sustituyen directamente, ya que el tipo de cierre podría pasarse fuera. Esto lleva al fracaso. Como lo señala @TC, el código puede considerarse mal formado, no se requiere diagnóstico, ya que la creación de instancias de s<foo>
produce una definición de plantilla (la del cierre) cuya segunda rama, if constexpr
no tiene instancias bien formadas. Esto explica el comportamiento de Clang y GCC.
Esto se reduce a un problema arquitectónico en una implementación importante (ver también esta respuesta ; GCC aparentemente no sufre de esta limitación), por lo que me sorprendería si Core considerara que su código está bien formado (después de todo, explicaron esto en el diseño de capturas lambda genéricas - vea la respuesta enlazada). GCC que admite su código es, en el mejor de los casos, una característica (pero probablemente dañina, ya que le permite escribir código dependiente de la implementación).