niespójność clang / gcc w specjalizacji klasowej

9

Natknąłem się na ten problem podczas próby specjalizacji tuple_size/ tuple_elementdla 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;
}

https://godbolt.org/z/ztuRSq

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?

ofo
źródło
3
Można to jeszcze bardziej uprościć .
Evg
3
ICC i MSVC również nie kompilują się.
ChrisMM
@Evg Zaskakujące, że to gcckompiluje, ponieważ nie kompiluje tego ...
Max Langhof
1
FWIW powinno być źle sformułowane, jeśli nie jestem całkowicie błędne (z tego samego powodu, że to jest źle uformowane).
Max Langhof,
1
ponieważ podajemy standard, dodałem tag prawnika języka.
Guillaume Racicot

Odpowiedzi:

3

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 zawsze T, 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_tto 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.

n314159
źródło
sfinae_v_t<T, std::is_integral_v<T>>i sfinae_v_t<T, !std::is_integral_v<T>>traktowane jako samych typów? Semantycznie nie są.
ofo
@GuillaumeRacicot Całkiem możliwe, ale chciałbym zrozumieć, dlaczego dokładnie. Na przykład standard mówi również : „Nazwy zależne nie mogą być sprawdzane przy deklaracji częściowej specjalizacji, ale będą sprawdzane przy zastępowaniu częściowej specjalizacji”. Czy to nie znaczy, że należy zdecydować, czy są tego samego typu, po zastąpieniu T częściową specjalizacją, ponieważ sfinae_v_t<T>jest to zależne T? W takim przypadku nie byłyby takie same, ponieważ jedno z nich będzie źle sformowane.
ofo
@ofo muszę powiedzieć, nie jestem pewien. Myślenie o tych dwóch jest trochę kłopotliwe, ponieważ jeden z nich nigdy nie będzie typem, a użycie ich obu w kontekście innym niż szablon spowoduje błąd kompilacji z powodu 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. intJest już podstawiona), a następnie w jednym z nich jest prawdziwy typ, więc nie musimy porównywać je abstrakcyjnie.
n314159
1
Kopanie głębiej, znalazłem ten z tutaj . SFINAE powinna dobrze działać z aliasami szablonów, w przeciwnym 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.
ofo