Problem polega na SFINAE. Jeśli przepisać swoją funkcję member aby być value_t<S<T>>
, jak poza deklaracją, następnie GCC chętnie go skompilować:
template<class T>
struct S
{
using value_type = int;
static const value_t<S<T>> C = 0;
};
template<class T>
const value_t<S<T>> S<T>::C;
Ponieważ wyrażenie jest teraz funkcjonalnie równoważne. Rzeczy takie jak nieudane zastąpienie wchodzą w grę na aliasach-szablonach, ale jak widać, funkcja członka value_type const C
nie ma tego samego "prototypu" jak value_t<S<T>> const S<T>::C
. Pierwszy nie musi wykonywać SFINAE, natomiast drugi wymaga tego. Więc wyraźnie obie deklaracje mają różną funkcjonalność, a więc napad złości GCC.
Co ciekawe, Clang kompiluje go bez śladów nienormalności. Zakładam, że tak się składa, że kolejność analiz Clanga jest odwrotna, w porównaniu do GCC. Po rozwiązaniu i uproszczeniu wyrażenia alias-szablon (tzn. Jest ono dobrze uformowane), clang porównuje oba deklaracje i sprawdza, czy są one równoważne (które w tym przypadku są, biorąc pod uwagę oba wyrażenia, które są w stanie rozstrzygnąć na value_type
).
Teraz, który z nich jest prawidłowy z oczu standardowego? Nadal nierozwiązaną kwestią jest to, czy należy uznać SFNIAE alias-template za część funkcji deklaracji. Cytowanie [temp.alias]/2:
gdy szablon ID odnosi się do specyfikacji matrycy ps, jest to równoważne skojarzonego typu uzyskanych poprzez zastąpienie jej szablonu-argumentów szablonu-parametrów typu-id aliasowy szablon.
Innymi słowy, te dwa są równoważne
template<class T>
struct Alloc { /* ... */ };
template<class T>
using Vec = vector<T, Alloc<T>>;
Vec<int> v;
vector<int, Alloc<int>> u;
Vec<int>
i vector<int, Alloc<int>>
równorzędne typów, ponieważ po podstawienie jest wykonane, obydwa typy końcu jest vector<int, Alloc<int>>
. Zwróć uwagę, że "po zamianie" oznacza, że równoważność jest sprawdzana tylko wtedy, gdy wszystkie argumenty szablonu zostaną zastąpione przez parametry szablonu. Oznacza to, że porównanie rozpoczyna się, gdy T
w vector<T, Alloc<T>>
zostanie zastąpione przez int
z Vec<int>
. Może to właśnie robi Clang z value_t<S<T>>
?Ale jest też następujący cytat z [temp.alias]/3:
Jeśli jednak identyfikator szablonu jest zależny, kolejne zastępowanie argumentu szablonu nadal ma zastosowanie do identyfikatora szablonu. [Przykład:
template<typename...> using void_t = void;
template<typename T> void_t<typename T::foo> f();
f<int>(); // error, int does not have a nested type foo
- przykład end]
Oto problem: ekspresja ma być również uformowana tak kompilator musi sprawdzić, czy zamiana jest w porządku. Gdy istnieje zależność w celu dokonania podstawienia argumentu szablonu (na przykład typename T::foo
), funkcjonalność całego wyrażenia zmienia się, a definicja "równoważności" jest różna. Na przykład, następujące kod nie kompilacji i szczęk (GCC):
struct X
{
template <typename T>
auto foo(T) -> std::enable_if_t<sizeof(T) == 4>;
};
template <typename T>
auto X::foo(T) -> void
{}
Ponieważ zewnętrzne foo
„S prototyp jest funkcjonalnie różna od komory wewnętrznej. Wykonanie auto X::foo(T) -> std::enable_if_t<sizeof(T) == 4>
zamiast tego powoduje, że kod kompiluje się poprawnie. Dzieje się tak dlatego, że typ zwrotu foo
jest wyrażeniem zależnym od wyniku sizeof(T) == 4
, więc po podstawieniu szablonu jego prototyp może być inny niż każda jego instancja. Podczas gdy typ zwrotu zwrotnego auto X::foo(T) -> void
nigdy nie jest inny, co jest sprzeczne z deklaracją wewnątrz X
. To jest ten sam problem, co dzieje się z kodem. Tak więc GCC wydaje się być poprawny w tym przypadku.
Wygląda na to, że powinny być równoważne: http://eel.is/c++draft/temp.alias#2 – Barry
Pójdę z błędem kompilatora dla 500, Alex – AndyG
Nie sądzę, że to takie trywialne . Istnieje wiele pytań dotyczących równoważności typów zależnych w odniesieniu do szablonów aliasów. Zobacz http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1979 i wszystkie problemy, które są z nim powiązane. Odpowiedź powinna obejmować to pytanie i znaczenie tych kwestii w tej sprawie, IMO. –