Oglądałem wykład Waltera Browna na Cppcon14 o nowoczesnym programowaniu szablonów ( część I , część II ), gdzie przedstawił swoją void_t
technikę SFINAE.
Przykład:
biorąc pod uwagę prosty szablon zmiennej, który ocenia, void
czy wszystkie argumenty szablonu są poprawnie sformułowane:
template< class ... > using void_t = void;
oraz następującą cechę, która sprawdza istnienie zmiennej składowej o nazwie member :
template< class , class = void >
struct has_member : std::false_type
{ };
// specialized as has_member< T , void > or discarded (sfinae)
template< class T >
struct has_member< T , void_t< decltype( T::member ) > > : std::true_type
{ };
Próbowałem zrozumieć, dlaczego i jak to działa. Dlatego mały przykład:
class A {
public:
int member;
};
class B {
};
static_assert( has_member< A >::value , "A" );
static_assert( has_member< B >::value , "B" );
1. has_member< A >
has_member< A , void_t< decltype( A::member ) > >
A::member
istniejedecltype( A::member )
jest dobrze uformowanyvoid_t<>
jest ważny i jest oceniany dovoid
has_member< A , void >
i dlatego wybiera specjalistyczny szablonhas_member< T , void >
i ocenia dotrue_type
2. has_member< B >
has_member< B , void_t< decltype( B::member ) > >
B::member
nie istniejedecltype( B::member )
jest źle ukształtowany i cicho zawodzi (sfinae)has_member< B , expression-sfinae >
więc ten szablon jest odrzucany
- kompilator znajdzie
has_member< B , class = void >
argument void jako domyślny has_member< B >
ocenia dofalse_type
Pytania:
1. Czy moje rozumienie tego jest prawidłowe?
2. Walter Brown stwierdza, że domyślny argument musi być dokładnie tego samego typu, co używany w programie void_t
, aby działał. Dlaczego? (Nie rozumiem, dlaczego te typy muszą pasować, czy żaden domyślny typ nie spełnia tego zadania?)
has_member<A,int>::value
. Wtedy częściowa specjalizacja, którejhas_member<A,void>
wynikiem jest ocena, nie może być zgodna. Dlatego musi to byćhas_member<A,void>::value
lub, w przypadku cukru składniowego, domyślny argument typuvoid
.has_member< T , class = void >
wpadania w zwłokęvoid
. Zakładając, że ta cecha będzie używana tylko z 1 argumentem szablonu w dowolnym momencie, to domyślny argument może być dowolnego typu?template <class, class = void>
natemplate <class, class = void_t<>>
. Więc teraz możemy robić, co chcemy, zvoid_t
implementacją szablonu aliasu :)Odpowiedzi:
1. Szablon klasy podstawowej
Podczas pisania
has_member<A>::value
kompilator wyszukuje nazwęhas_member
i znajduje podstawowy szablon klasy, czyli taką deklarację:(W OP jest to zapisane jako definicja).
Lista argumentów szablonu
<A>
jest porównywana z listą parametrów szablonu tego szablonu podstawowego. Ponieważ podstawowy szablon ma dwa parametry, ale tylko w zestawie jeden, pozostałe parametr jest domyślnie domyślnego szablonu argumentu:void
. To tak, jakbyś napisałhas_member<A, void>::value
.2. Specjalistyczny szablon klasy
Teraz lista parametrów szablonu jest porównywana z dowolnymi specjalizacjami szablonu
has_member
. Tylko wtedy, gdy żadna specjalizacja nie pasuje, definicja szablonu podstawowego jest używana jako rozwiązanie rezerwowe. Tak więc brana jest pod uwagę specjalizacja częściowa:Kompilator próbuje dopasować argumenty szablonu
A, void
do wzorców zdefiniowanych w częściowej specjalizacji:T
ivoid_t<..>
jeden po drugim. Najpierw wykonywana jest dedukcja argumentów szablonu. Powyższa częściowa specjalizacja to nadal szablon z parametrami szablonu, które muszą być „wypełnione” argumentami.Pierwszy wzorzec
T
pozwala kompilatorowi wydedukować parametr szablonuT
. Jest to trywialna dedukcja, ale rozważ wzorzecT const&
, w którym nadal moglibyśmy wywnioskowaćT
. Dla wzorcaT
i argumentu szablonu wnioskujemyA
, żeT
jestA
.W drugim wzorcu
void_t< decltype( T::member ) >
parametr-szablonT
pojawia się w kontekście, w którym nie można go wywnioskować z żadnego argumentu szablonu.Odliczenie argumentu szablon jest zakończona (*) , teraz to wywnioskowane argumenty szablonu są podstawione. Tworzy to specjalizację, która wygląda następująco:
void_t< decltype( A::member ) >
Teraz można ocenić typ . Jest dobrze uformowany po zmianie, dlatego nie występuje żadna awaria zamiany . Otrzymujemy:3. Wybór
Teraz możemy porównać listę parametrów szablonu tej specjalizacji z argumentami szablonu dostarczonymi do oryginału
has_member<A>::value
. Oba typy pasują dokładnie, więc wybrana jest ta częściowa specjalizacja.Z drugiej strony, gdy zdefiniujemy szablon jako:
Kończymy z tą samą specjalizacją:
ale na razie nasza lista argumentów szablonów
has_member<A>::value
to<A, int>
. Argumenty nie pasują do parametrów specjalizacji, a szablon podstawowy jest wybierany jako rezerwowy.(*) Standard, IMHO myląco, obejmuje proces podstawiania i dopasowywanie jawnie określonych argumentów szablonu w procesie dedukcji argumentów szablonu . Na przykład (po N4296) [temp.class.spec.match] / 2:
Ale to nie tylko oznacza, że należy wydedukować wszystkie parametry szablonu częściowej specjalizacji; oznacza to również, że podstawienie musi się powieść i (jak się wydaje?) argumenty szablonu muszą być zgodne z (podstawionymi) parametrami szablonu częściowej specjalizacji. Zauważ, że nie jestem całkowicie świadomy tego, gdzie Standard określa porównanie między podstawioną listą argumentów a dostarczoną listą argumentów.
źródło
Że powyższa specjalizacja istnieje tylko wtedy, gdy jest dobrze ukształtowana, a więc kiedy
decltype( T::member )
jest ważna i niejednoznaczna. specjalizacja jest taka,has_member<T , void>
jak zaznaczam w komentarzu.Kiedy piszesz
has_member<A>
, dzieje sięhas_member<A, void>
tak z powodu domyślnego argumentu szablonu.Mamy specjalizację dla
has_member<A, void>
(więc dziedzicz ztrue_type
), ale nie mamy specjalizacji dlahas_member<B, void>
(więc używamy domyślnej definicji: dziedziczenie zfalse_type
)źródło