2010-12-14 16 views
5

Książka C++ Templates : The Complete Guide ma przykład na stronie 275, że mam problem z zawijaniem głowy.Dlaczego te szablony są niejednoznaczne?

Cytowanie fragmentów z książki ...

template <typename T> 
class Promotion<T,T> { 
    public: 
    typdef T ResultT; 
}; 

template<typename T1, typename T2> 
class Promotion<Array<T1>, Array<T2> > { 
    public: 
    typedef Array<typename Promotion<T1,T2>::ResultT> ResultT; 
}; 

template<typename T> 
class Promotion<Array<T>, Array<T> > { 
    public: 
    typedef Array<typename Promotion<T,T>::ResultT> ResultT; 
}; 

Niestety, częściowa specjalizacja Promotion<Array<T1>, Array<T2> > jest ani więcej, ani mniej wyspecjalizowane niż częściowej specjalizacji Promotion<T,T>. Aby uniknąć niejednoznaczności wyboru szablonu, dodano ostatnią częściową specjalizację . Jest bardziej wyspecjalizowany niż jedna z poprzednich częściowych specjalizacji.

Dlaczego dwa pierwsze szablony są niejednoznaczne i dlaczego ostatni szablon rozwiązuje problem niejednoznaczności? Kiedy próbuję zastosować reguły, albo nie mogę wymyślić, jak powstaje dwuznaczność, albo jeśli myślę, że mam sposób, żeby to się stało, nie wiem, dlaczego ostatni szablon rozwiązuje problem.

+1

Bez ostatniej specjalizacji, co by było wybrane, gdybyśmy spróbowali użyć 'Promotion , Array >'? –

+0

Więc ten ostatni rozwiązuje problem, ponieważ kompilator uważa, że ​​promocja , Array > jest dokładnie zgodna? – person

Odpowiedz

14

Być może twoje zamieszanie wynika z tego, jak działa relacja "jest bardziej wyspecjalizowana niż". Jest to częściowe zamówienie, a nie całkowita kolejność - co oznacza, że ​​przy dwóch specjalnych specjalizacjach szablonowych nie zawsze jest tak, że jeden jest bardziej wyspecjalizowany niż drugi.

komentarz Anon jest słuszna: Załóżmy, że 3-ci specjalizacja nie istnieje, a później w kodzie miałeś:

Promotion<Array<double>, Array<double> > foo; 

(Oczywiście, że prawdopodobnie nie będzie faktycznie utworzyć zmienną tego typu struct pusty , ale jest to najprostszy sposób na wymuszenie jego wystąpienia.)

Biorąc pod uwagę tę deklarację foo, która z 2 pierwszych specjalizacji zostanie wybrana?

  • Specjalizacja 1 dotyczy, z T = Array<double>.
  • Specjalizacja 2 dotyczy, z T1 = double, T2 = double.

Obydwie specjalizacje mają zastosowanie, więc musimy określić, który "jest bardziej wyspecjalizowany niż" inny i wybrać ten. W jaki sposób? Powiemy, że X jest bardziej wyspecjalizowane niż Y jeśli jest przynajmniej jako wyspecjalizowany jak Y, ale Y nie jest przynajmniej jako wyspecjalizowane X. Chociaż wydaje się, że jest to po prostu tańczyć wokół problemu, jest mądra zasada, że ​​możemy użyć, aby odpowiedzieć na to nowe pytanie:

X jest przynajmniej jako wyspecjalizowane Y jeśli, niezależnie od tego, jakiego rodzaju możemy przypisać do parametry szablonu X, wynikowy typ może zawsze być dopasowany przez Y.

Należy pamiętać, że zapominamy o poszczególnych typach zaangażowanych w bieżące tworzenie (w tym przypadku double) - relacja "jest co najmniej tak specjalistyczna jak" jest własnością samych specjalizacji cząstkowych, a nie zależy od konkretnych wystąpień.

Czy specjalizacja 1 zawsze może być dopasowana do specjalizacji 2? Proces jest trochę podobny do algebry. Wymagamy, aby każdy dla typu T możemy znaleźć typy T1 i T2 takie, że:

Promotion<Array<T1>, Array<T2> > = Promotion<T, T> 

Oznacza to:

Array<T1> = T 
Array<T2> = T 

Więc odpowiedź brzmi: nie. Patrząc tylko na pierwszy domniemany wynik, biorąc pod uwagę dowolny typ T, generalnie nie można znaleźć typu T1 tak, że Array<T1> jest tego samego typu co T. (To działa, jeśli T akurat Array<long>, ale nie jeśli T jest int lub char* lub większości innych typów.)

Co na odwrót? Czy specjalizacja 2 zawsze może być dopasowana do specjalizacji 1?Wymagamy dla wszelkie rodzaje T1 i T2 możemy znaleźć typ T takie, że:

Promotion<T, T> = Promotion<Array<T1>, Array<T2> > 

sugerując:

T = Array<T1> 
T = Array<T2> 

Więc odpowiedź jest znowu nie. Biorąc pod uwagę dowolny typ T1, zawsze można znaleźć typ T taki, że T jest tego samego typu co Array<T1> - po prostu dosłownie ustawiony T = Array<T1>. Ale w ogóle inny typ T2 nie jest ograniczony do być taka sama jak T1, a jeśli to nie jest (na przykład jeśli T1 = bool ale T2 = float), to nie będzie możliwe, aby znaleźć typ T która jest taka sama zarówno Array<T1> i Array<T2>. Zasadniczo nie jest możliwe znalezienie takiego typu T.

W tym przypadku nie tylko nie specjalizacja jest bardziej wyspecjalizowana niż druga, ani nie jest nawet wyspecjalizowana jako. W rezultacie, jeśli zajdzie taka potrzeba, aby utworzyć tę klasę szablonów i obie specjalizacje są zgodne - tak jak w przykładzie, który dał Anon - nie ma możliwości wyboru "najlepszego".

+2

Gdybym miał konta na sockpuppet, dałbym +1 od każdego z nich. Niestety, to moje jedyne konto, więc mogę tylko +1 sumy. –

+0

@ James: Thanks! :) –

+1

Po raz pierwszy widzę rozsądne wytłumaczenie tego, do tej pory po prostu zgadłem, i choć działa dobrze (zwykle), doceniam posiadanie racjonalnego uzasadnienia! –

2

Wieloznaczność jest po tej samej klasy mogą być tworzone wystąpienia z różnych specjalnościach i kompilator nie może faworyzować jednej specjalizacji nad drugim, bo nie jest bardziej wyspecjalizowane. Dodanie bardziej wyspecjalizowanej wersji sprawia, że ​​kompilator wybiera tę wersję. To rozwiązuje niejednoznaczność, ponieważ reguły C++ nakazują wybrać najbardziej wyspecjalizowany szablon w oparciu o mniej wyspecjalizowane szablony.

3

To dlatego, że podczas wyciągania parametry, jeden szablon jest lepsze dla pierwszego parametru, ale druga jest lepsza dla sekundę. wygląd

Miejmy na Promotion< Array<S>, Array<S> >.

Obaj kandydaci mogą się równać. Przy pierwszym parametrze Promotion< T, T > dopasowuje, dedukując T = Array<S>. Promotion< T1, T2 > mecze przez odliczenie T1 = S. Drugi jest lepszym rozwiązaniem, ponieważ Array<T1> jest bardziej szczegółowy niż T.

Na drugim parametrze Promotion< T = Array<S>, T = Array<S> > jest dokładne dopasowanie. Promotion< T1 = S, T2 > mecze przez dedukowanie T2 = S. Ponieważ Array<S> jest lepszym rozwiązaniem niż Array<T2>, pierwsze jest bardziej szczegółowe.

Zasady wyboru najlepszego szablonu powiedzieć, że jeden parametr musi być lepszy mecz, a wszystkie inne być nie gorsze w porównaniu do wszystkich innych kandydatów. Ponieważ żaden kandydat nie spełnia tych kryteriów, jest niejednoznaczny.

Trzecia specjalizacja spełnia kryteria (pierwszy parametr jest tak dobry, jak Array<T1>, drugi jest doskonały), więc rozwiązuje niejednoznaczność.

Jeśli NAPRAWDĘ chcesz zakręcić głową, spróbuj przeczytać sekcję [temp.deduct.partial] w standardzie, gdzie wszystkie te zasady są podane w dobrym adwokacie. To 14.8.2.4 w wersji roboczej n3225, numeracja może się różnić w innych wersjach.

+0

+1 ale twoje wyjaśnienie "co najmniej jeden parametr musi być lepiej dopasowany, a inne nie gorsze" jest tutaj nieznacznie - ta reguła jest używana do decydowania między ogólnie przeładowanymi funkcjami, i to tylko wtedy, gdy się to nie udaje (tj. wszystkie argumenty mają równie dobre sekwencje konwersji), w których szuka się najbardziej wyspecjalizowanych szablonów.Tak, to mylące jak diabli, musiałem samemu sprawdzić standard;) –

+0

@j_random_hacker: Masz rację, tutaj są parametry szablonu, a nie argumenty funkcji. Zdecydowanie lepiej "nie gorzej" wyjaśniono w odpowiedzi, ale ... zasada wydaje się być całkiem podobna do zasady nadmiernego rozładowania, dopóki nie zagłębisz się w szczegóły "mniej wyspecjalizowanych". –

3

j_random_hacker ma answered pytanie, ale jako konkretny przykład tylko przy użyciu reguł średnia bezpośrednio:

template< class T > 
class Array {}; 

template< class T1, class T2 > 
class Promotion {}; 

template <typename T>     // a 
class Promotion<T,T> { 
public: 
    typedef T ResultT; 
}; 

template<typename T1, typename T2>  // b 
class Promotion<Array<T1>, Array<T2> > { 
public: 
    typedef Array<typename Promotion<T1,T2>::ResultT> ResultT; 
}; 

// template<typename T> 
// class Promotion<Array<T>, Array<T> > { 
// public: 
//  typedef Array<typename Promotion<T,T>::ResultT> ResultT; 
// }; 


//---------------------------- §14.5.4.2/1: 

template< class T > 
void a_(Promotion< T, T >);      // a 

template< class T1, class T2 > 
void b_(Promotion< Array<T1>, Array<T2> >); // b 


//---------------------------- §14.5.5.2/3: 

class aT {}; 
class bT1 {}; 
class bT2 {}; 

void a(Promotion< aT, aT >);       // a 
void b(Promotion< Array<bT1>, Array<bT2> >);  // b 

void test() 
{ 
    // Check if the concrete 'a' arguments fit also 'b': 
    b_(Promotion< aT, aT >()); 
    // Fails, so a is not at least as specialized as b 

    // Check if the concrete 'b' arguments fit also 'a': 
    a_(Promotion< Array<bT1>, Array<bT2> >()); 
    // Fails, so b is not at least as specialized as a 
}  

Wsparcie musiałem ponownie nauczyć się tego ponownie.

Zamieszczam to, ponieważ ktoś inny ™ znalazł mój przykład oświecający, więc może pomóc także czytelnikom tutaj.

Cheers & HTH.,

+0

+1, nie byłem w stanie dowiedzieć się, jakie są wartości 'a()' i 'b()' ... –

+0

@j_random_hacker: częściowe porządkowanie szablonów klas jest definiowane w kategoriach częściowego porządkowania dla odpowiednich szablonów funkcji ... są to szablony funkcyjne odpowiadające różnym specjalizacjom "szablonu klasa Promocja" –

+0

@Ben Voigt: To wyjaśnia szablony funkcji 'a _()' i 'b _()', ale nie funkcje bez szablonu ' a() 'i' b() '. –

2

Spójrz na to, co zrobić specjalizacje na płaszczyźnie koncepcyjnej.

Pierwsza z nich mówi: "To właśnie powinniśmy zrobić, jeśli T1 i T2 są takie same."

Drugi mówi: "To właśnie powinniśmy zrobić, jeśli T1 i T2 są szablonami Array".

OK, więc ... załóżmy, że T1 i T2 to oba z tym samym szablonem tablicowym. Którego powinniśmy użyć? Nie ma dobrego sposobu, aby twierdzić, że jeden z nich jest bardziej dostosowany do tej sytuacji niż drugi.

Rozwiązujemy to poprzez dodanie trzeciej specjalności szablonów specjalnie dla tej sytuacji: "To właśnie powinniśmy zrobić, jeśli T1 i T2 są tym samym szablonem Array".

Powiązane problemy