c++ - MĂșltiples especializaciones de plantilla de clase SFINAE usando void_t
c++17 template-specialization (2)
Existe una regla según la cual las especializaciones parciales tienen que ser más especializadas que la plantilla principal: ambas especializaciones siguen esa regla. Pero no hay una regla que establezca que las especializaciones parciales nunca pueden ser ambiguas. Es más que si la creación de instancias conduce a una especialización ambigua, el programa está mal formado. ¡Pero esa instanciación ambigua tiene que suceder primero!
Parece que Clang sufre de CWG 1558 aquí y está demasiado ansioso por sustituir . std::void_t
en void
Esto es CWG 1980 casi exactamente:
En un ejemplo como
template<typename T, typename U> using X = T; template<typename T> X<void, typename T::type> f(); template<typename T> X<void, typename T::other> f();
parece que la segunda declaración de
f
es una redeclaración de la primera pero distinguible por SFINAE, es decir, equivalente pero no funcionalmente equivalente.
Si utiliza la implementación no alias de void_t
:
template <class... Ts> struct make_void { using type = void; };
template <class... Ts> using void_t = typename make_void<Ts...>::type;
luego clang permite las dos especializaciones diferentes. Claro, la has_members
instancias de has_members
en un tipo que tiene type1
type2
type1
y type2
, pero eso es lo que se espera.
¿Son válidas las especializaciones de plantilla de clase múltiple, cuando cada una es distinta solo entre patrones que involucran parámetros de plantilla en contextos no deducidos?
Un ejemplo común de std::void_t
usa para definir un rasgo que revela si un tipo tiene un miembro typedef
llamado "tipo". Aquí, se emplea una sola especialización. Esto podría extenderse para identificar, por ejemplo, si un tipo tiene un miembro typedef
llamado "type1" o uno llamado "type2". El siguiente código de C ++ 1z se compila con GCC, pero no con Clang. ¿Es legal?
template <class, class = std::void_t<>>
struct has_members : std::false_type {};
template <class T>
struct has_members<T, std::void_t<typename T::type1>> : std::true_type {};
template <class T>
struct has_members<T, std::void_t<typename T::type2>> : std::true_type {};
No creo que sea correcto, o al menos, no si creamos una instancia de has_members con un tipo que tenga tanto el tipo 1 como el tipo 2 anidados, el resultado serían dos especializaciones que son
has_members<T, void>
Lo cual no sería válido. Hasta que se cree una instancia del código, creo que está bien, pero Clang lo rechaza antes. En g ++, falla con este caso de uso, una vez instanciado:
struct X
{
using type1 = int;
using type2 = double;
};
int main() {
has_members<X>::value;
}
El mensaje de error no parece describir el problema real, pero al menos se emite:
<source>:20:21: error: incomplete type ''has_members<X>'' used in nested name specifier
has_members<X>::value;
^~~~~
Si lo creas con un tipo que solo tiene type1 o type2 pero no ambos, entonces g ++ lo compila limpiamente. Por lo tanto, se opone al hecho de que los miembros están presentes, lo que causa ejemplificaciones conflictivas de la plantilla.
Para obtener la disyunción, creo que querrías un código como este:
template <class, class = std::void_t<>>
struct has_members : std::bool_constant<false> {};
template <class T>
struct has_members<T, std::enable_if_t<
std::disjunction<has_member_type1<T>, has_member_type2<T>>::value>> :
std::bool_constant<true> {};
Esto supone que tienes rasgos para determinar que has_member_type1 y has_member_type2 ya están escritos.