2017-01-13 31 views
41

Portu niektórych kodu C++ 11 z Clang do g ++Dlaczego szablon aliasu podaje sprzeczną deklarację?

template<class T> 
using value_t = typename T::value_type; 

template<class> 
struct S 
{ 
    using value_type = int; 
    static value_type const C = 0; 
}; 

template<class T> 
value_t<S<T>> // gcc error, typename S<T>::value_type does work 
const S<T>::C; 

int main() 
{  
    static_assert(S<int>::C == 0, ""); 
} 

daje różne zachowanie dla dzyń (wersje 3.1 przez SVN bagażniku) w stosunku do każdej wersji g ++. Dla tych ostatnich pojawiają się błędy like this

prog.cc:13:13: error: conflicting declaration 'value_t<S<T> > S< <template-parameter-1-1> >::C' 
const S<T>::C; 
      ^
prog.cc:8:29: note: previous declaration as 'const value_type S< <template-parameter-1-1> >::C' 
    static value_type const C = 0; 
          ^
prog.cc:13:13: error: declaration of 'const value_type S< <template-parameter-1-1> >::C' outside of class is not definition [-fpermissive] const S<T>::C; 

Jeśli zamiast szablonu alias value_t<S<T>> używam pełnego typename S<T>::value_type następnie g++ also works.

Pytanie: nie są to aliasy szablonów, które mają być całkowicie zamienne z ich podstawowym wyrażeniem? Czy to jest błąd g ++?

Aktualizacja: Visual C++ akceptuje również szablon aliasu w definicji poza klasą.

+4

Wygląda na to, że powinny być równoważne: http://eel.is/c++draft/temp.alias#2 – Barry

+4

Pójdę z błędem kompilatora dla 500, Alex – AndyG

+8

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. –

Odpowiedz

6

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.