Natknąłem się na ten problem podczas próby specjalizacji tuple_size
/ tuple_element
dla niestandardowej klasy w C ++ 17 dla powiązań strukturalnych.
Poniższy kod kompiluje się w GCC, ale nie w clang (obie wersje trunk, patrz poniższy link).
#include <type_traits>
template<typename T, typename... Ts>
using sfinae_t = T;
template<typename T, bool... Bs>
using sfinae_v_t = sfinae_t<T, typename std::enable_if<Bs>::type...>;
template <typename T>
struct Test;
template <typename T>
struct Test<sfinae_v_t<T, std::is_integral_v<T>>> {};
void f() {
Test<int> t;
}
To jest błąd dostarczany przez clang:
<source>:13:8: error: class template partial specialization does not specialize any template argument; to define the primary template, remove the template argument list
struct Test<sfinae_v_t<T, std::is_integral<T>::value>> {};
^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1 error generated.
Compiler returned: 1
Czy to błąd w kompilatorze, czy powyższy kod wywołuje niektóre UB?
gcc
kompiluje, ponieważ nie kompiluje tego ...Odpowiedzi:
To, co powiem poniżej (w ramach OLD POST ), powinno być do pewnego stopnia prawdziwe, ale faktyczny problem polega na tym, że SFINAE jest niewłaściwie używana, dlatego nie jestem już pewien, czy to błąd w gcc.
Deklaracja aliasu musi zawsze kończyć się powodzeniem, nie można tam SFINAE, ponieważ nie jest to deklaracja klasy ani funkcji ani specjalizacja (to ma sens, ponieważ nie można specjalizować aliasów). Jeśli deklaracja aliasu nie powiedzie się, program jest źle sformułowany. Dlatego kompilator może założyć, że nigdy nie dojdzie do przypadku, że deklaracja aliasu nie powiedzie się, dopóki nie zmusisz go do utworzenia takiego szablonu.
Dlatego jest całkowicie dopuszczalne, aby kompilator myślał, że tak
sfinae_v_t<T,...>
jest zawszeT
, ponieważ tak się stanie, gdy program nie będzie źle sformułowany. Dlatego zobaczy, że we wszystkich przypadkach, w których program nie jest źle sformułowany, częściowa specjalizacja nie specjalizuje się i jako taka powie ci, że jest źle sformułowana. (Tak właśnie działa klang).Nie sądzę, że kompilator jest do tego zmuszony. A jeśli tak nie jest i po prostu myśli „Ok,
sfinae_v_t
to jakiś typ, cokolwiek.”, To nie jest oczywiste, że jest to powtórna deklaracja. Myślę więc, że dopóki nie stworzymy jednej z nich, nie ma nic złego w tym, że nie wyrzucimy błędu.Ale kiedy go tworzymy, powinien występować problem polegający na tym, że mamy redeklarację lub że program jest źle sformułowany
std::enable_if
, w zależności od argumentu szablonu. GCC powinno wybrać przynajmniej jedną z nich, ale nie ma żadnej.Nie dotyczy to również absolutnie łatwiejszego przykładu bez
std::enable_if
. Nadal uważam, że jest to błąd w GCC, ale jestem wystarczająco oszołomiony, że nie mogę już tego powiedzieć z całą pewnością. Powiedziałbym tylko, że ktoś powinien zgłosić to jako błąd i pozwolić ludziom z gcc myśleć o tym.STARY POST
To jest błąd w gcc. Standard podaje nam zasady przekształcania szablonu klasy w szablony funkcji. Jeden szablon klasy jest bardziej wyspecjalizowany niż inny, jeśli jego funkcja jest ważniejsza niż druga w częściowym uporządkowaniu szablonu funkcji.
Utworzyłem tutaj funkcje , a teraz gcc twierdzi, że wywoływanie ich jest niejednoznaczne, dlatego musiałbym również powiedzieć, że szablony klas są jednakowo określone.
Uwaga: po dokładnym przeczytaniu standardu kompilator w mojej głowie zgadza się z clang.
źródło
sfinae_v_t<T, std::is_integral_v<T>>
isfinae_v_t<T, !std::is_integral_v<T>>
traktowane jako samych typów? Semantycznie nie są.sfinae_v_t<T>
jest to zależneT
? W takim przypadku nie byłyby takie same, ponieważ jedno z nich będzie źle sformowane.enable_if_t
. Mój najlepszy odczyt standardu jest taki, że nie ma znaczenia, czy są one takie same, czy nie. W przypadku częściowego uporządkowania zawsze będziemy porównywać formę parametru templare jednej funkcji z szablonową formą argumentu drugiej (tj.int
Jest już podstawiona), a następnie w jednym z nich jest prawdziwy typ, więc nie musimy porównywać je abstrakcyjnie.template<bool B, typename T> enable_if_t = typename enable_if<B, T>::type;
razie też nie działałaby. Pójdę naprzód i zgłoś błąd przeciwko gcc, ale nie jestem do końca pewny, czy gcc jest tam źle. Dzięki.