2015-07-13 34 views
30
#include <iostream> 

template<typename T> 
struct identity 
{ 
    typedef T type; 
}; 

template<typename T> void bar(T) { std::cout << "a" << std::endl; } 
template<typename T> void bar(typename identity<T>::type) { std::cout << "b" << std::endl; } 

int main() 
{ 
    bar(5); // prints "a" because of template deduction rules 
    bar<int>(5); // prints "b" because of ...? 

    return EXIT_SUCCESS; 
} 

Spodziewałem się, że bar<int>(5) wywoła co najmniej dwuznaczność. Jaka szalona reguła dotyczy rozdzielczości przeciążania funkcją szablonu?Ten przypadek przeciążania funkcji szablonów umyka mojej intuicji:

+6

Powiedziałbym, że 'tożsamość typograficzna :: typ' jest bardziej wyspecjalizowana niż 'T'. – Jarod42

+0

Mam to. Byłbym wdzięczny za pełną odpowiedź, którą mogę zaakceptować. – gd1

+0

@ Jarod42 'typename identity :: type' jest tylko syntaktycznie bardziej gadatliwym sposobem wyrażania 'int'; nie oznacza bardziej wyspecjalizowanego rodzaju "int". Jeśli podstawimy "int" dla parametru szablonu "T" w obu funkcjach, otrzymamy taki, który po prostu bierze "int", a inny, który pobiera "typename identity :: type". ** To prawdopodobnie powinno być dwuznaczne **. Mam na myśli, załóżmy, że masz zwykłą prostą klasę A, która zawiera 'typedef int B'. Nie dajesz przeciążenia preferencji funkcji, która używa 'A :: B' zamiast takiej, która używa' int' w tej samej pozycji parametru. – Kaz

Odpowiedz

17

Gdy dostajemy nasze funkcje kandydujące Set (oba bar s), a następnie Whittle go do żywotnych funkcji (jeszcze oba bar s) musimy ustalić najlepszą realną funkcję. Jeśli jest więcej niż jeden, otrzymujemy błąd dwuznaczności. Kroki bierzemy do określenia najlepszy zostały określone w [over.match.best]:

[A] realną funkcyjnego F1 określa się lepiej niż innym funkcja realną funkcyjnego F2, jeżeli dla wszystkich argumentów i ICS i (F1) nie jest gorszy sekwencja konwersji niż ICS i (F2), a następnie
- jakiegoś argumentu j ICS j (F1) to lepiej sekwencja konwersji niż ICS j (F2), lub, jeśli nie to,

Obie funkcje przyjmują argument typu int, więc obie sekwencje konwersji są identyczne. Kontynuujemy.

- kontekst jest inicjowanie przez zdefiniowany przez użytkownika konwersji [...]

Nie dotyczy.

- kontekst jest inicjowanie przez funkcję konwersji do bezpośredniego wiązania odniesienia (13.3.1.6) z odniesieniem do funkcjonowania rodzaju [...]

Nie dotyczy.

- F1 nie jest funkcją szablonu specjalizacji i F2 jest funkcją szablon specjalizacji, lub, jeśli nie, że

obu bar<int> s są specjalizacje szablon funkcji. Przechodzimy więc do ostatniego punktu wypunktowania, aby określić najlepszą możliwą do zrealizowania funkcję.

- F1 i F2 są funkcyjne specjalizacje szablonu i szablon funkcja F1 jest bardziej wyspecjalizowany niż szablon do F2 zgodnie z zasadami zamawiania częściowych opisanych w 14.5.6.2.

cząstkowe zasady zamawiania w zasadzie sprowadzają się do nas syntetyzowania nowych unikalne typy argumentów dla obu bar przeciążeń i wykonywania szablonów odliczenia na drugim przeciążenia.

Najpierw należy rozważyć przeciążenie "b". Zsyntetyzuj typ typename identity<Unique1>::type i spróbuj wykonać dedukcję szablonu względem T. To się udało. Najprostsza obsada dedukcji szablonów.

Następnie należy rozważyć przeciążenie "a". Zsyntetyzuj typ Unique2 i spróbuj wykonać dedukcję szablonu wobec typename identity<T>::type. To nie powiedzie się! Jest to kontekst nie dedukowany - żadna dedukcja nie może się powieść.

Ponieważ dedukcja typu szablonu odnosi sukces tylko w jednym kierunku, przeciążenie bar(typename identity<T>::type) jest uważane za bardziej wyspecjalizowane i jest wybierane jako najlepszy kandydat.


bogdan prezentuje kolejny ciekawy przypadek dla patrząc na częściowego uporządkowania. Rozważmy zamiast porównywania:

template <typename T> void bar(T, T); // "c" 
template <typename T> void bar(T, typename identity<T>::type); // "d" 

bar(5,5); 
bar<int>(5, 5); 

Znowu obaj kandydaci są opłacalne (tym razem nawet bez wyraźnego określenia T), więc patrzymy na częściowe reguły zamawiania.

Dla przeciążenia "c" syntetyzujemy argumenty typu UniqueC, UniqueC i podejmujemy próbę odliczenia od T, typename identity<T>::type. To się uda (z T == UniqueC). Zatem "c" jest co najmniej tak wyspecjalizowane jak "d".

W przypadku przeciążenia "d" syntetyzujemy argumenty typu UniqueD, typename identity<UniqueD>::type i podejmujemy próbę odliczenia od T, T. To się nie udaje! Argumenty są różnych typów! Zatem "d" nie jest co najmniej tak wyspecjalizowane jak "c".

W ten sposób wywoływane jest przeciążenie "c".

+1

Myślę, że warto zauważyć, że proponowana rezolucja [z czynnym numerem 1391] (http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#1391) prawdopodobnie uczyni to niejednoznacznym. Cały ten obszar jest źle określony - po prostu zmień pierwszą deklarację na "bar (T, T)", a drugą na 'bar (T, tożsamość na typie :: type)' i ciesz się niespodzianką ... – bogdan

+0

@bogdan Yeah warto dodać jako przykład. To interesujące! – Barry

+1

"Dla przeciążenia" c ", [...]" - nie możemy oczekiwać, że kompilator będzie wiedział, co 'identity :: type' jest dla * any * wymyślił' UniqueC', więc odliczanie nie może faktycznie się powieść podczas częściowego zamawiania dla tego przypadku. W szczególności nie możemy ogólnie powiedzieć, że 'identity :: type' jest zawsze' T', co pozwala nam odnieść sukces. Gdybyśmy mogli, moglibyśmy zrobić to samo dla przeciążenia "d", kiedy syntetyzujemy argumenty typu 'UniqueD, nazwa_testowa :: type' - będą one równoważne z' UniqueD, UniqueD', dzięki czemu dedukcja zakończy się sukcesem. w inny sposób. – bogdan