2009-11-24 18 views
9

Miałem problem z opisem opisanym w this question (deklarowanie funkcji szablonu jako znajomego klasy szablonu) i uważam, że druga odpowiedź jest tym, co chcę zrobić (przesyła dalej szablon funkcja, następnie nazwij specjalizację jako przyjaciela). Mam pytanie, czy nieco inne rozwiązanie jest rzeczywiście poprawna lub po prostu zdarza się pracować w Visual C++ 2008.Funkcja przyjaciela szablonu szablonu klasy

kod testu jest:

#include <iostream> 

// forward declarations 
template <typename T> 
class test; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t); 

template <typename T> 
class test { 
    friend std::ostream& operator<< <T>(std::ostream &out, const test<T> &t); 
    // alternative friend declaration 
    // template <typename U> 
    // friend std::ostream& operator<<(std::ostream &out, const test<T> &t); 

    // rest of class 
    }; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t) { 
    // output function defined here 
    } 

Najpierw jedna dziwna rzecz znalazłem było to, że jeśli zmienię przednia deklaracja operator<<, tak aby się nie zgadzała (na przykład, std::ostream& operator<<(std::ostream &out, int fake);, wszystko nadal się kompiluje i działa poprawnie (dla jasności, nie muszę definiować takiej funkcji, tylko ją deklaruję) .Jednak jak w połączone z pytaniem, usunięcie deklaracji forward powoduje problem, ponieważ wydaje się, że kompilator deklaruje element danych zamiast funkcji znajomego. Jestem prawie pewien, że to zachowanie jest błąd Visual C++ 2008.

Ciekawostką jest usunięcie oświadczeń typu "forward" i użycie alternatywnej deklaracji znajomego w powyższym kodzie. Zauważ, że parametr szablonu U nie pojawia się w następującym sygnaturze. Ta metoda również kompiluje i działa poprawnie (bez zmiany czegokolwiek innego). Moje pytanie brzmi, czy jest to zgodne ze standardem, czy idiosynkrazą programu Visual C++ 2008 (nie mogłem znaleźć dobrej odpowiedzi w moich podręcznikach).

pamiętać, że deklaracja przyjaciel template <typename U> friend ... const test<U> &t); działa również, to rzeczywiście daje każdemu wystąpienie operator friend dostępu do jakiejkolwiek instancji test, natomiast to, co chcę jest, że prywatne członkowie test<T> powinny być dostępne tylko z operator<< <T>. Przetestowałem to, tworząc instancję test<int> wewnątrz operator<< i uzyskując dostęp do prywatnego elementu; powinno to spowodować błąd kompilacji podczas próby wyprowadzenia test<double>.

Streszczenie: Usunięcie deklaracji forward i przejście na alternatywną deklarację przyjaciela w powyższym kodzie wydaje się dawać taki sam wynik (w Visual C++ 2008) - czy ten kod jest rzeczywiście poprawny?

AKTUALIZACJA: Każda z powyższych modyfikacji kodu nie działa w gcc, więc domyślam się, że są to błędy lub "cechy" kompilatora Visual C++. Wciąż doceniam spostrzeżenia od osób znających standard.

+1

Nawiasem mówiąc, doszedłem do wniosku, że dodanie 'using namespace std;' zanim deklaracja klasy usunie potrzebę deklaracji forward, co jak przypuszczam jest efektem ubocznym (możliwego) błędu kompilatora. –

+0

Zgodnie z moim komentarzem - czy możesz wyraźnie dodać przykłady (w tym tworzenie instancji), o których mówisz, że działają? Werbalnie trudno jest zrozumieć, co masz na myśli dla każdego przykładu. –

Odpowiedz

7

... gdybym < < tak zmienić naprzód oświadczenie wykonawcy, że nie pasuje

Funkcja przyjaciel powinien być postrzegany jako bardzo szczególnego rodzaju deklaracji. Zasadniczo kompilator robi wystarczająco dużo, aby przeanalizować deklarację, jednak żadne semantyczne sprawdzanie nie będzie miało miejsca, chyba że faktycznie wyspecyfikujesz klasę.

Po dokonaniu sugerowaną zmianę, jeśli następnie instancję test dostaniesz błąd o oświadczeniach nie pasującymi:

template class test<int>; 

... Jednak ... usunięcie deklarację do przodu powoduje problem

Kompilator próbuje przeanalizować deklarację, aby zapisać, dopóki szablon klasy nie będzie wyspecjalizowany.Podczas parsowania, kompilator osiągnie < w deklaracji:

friend std::ostream& operator<< < 

Jedynym sposobem operator<< mogłoby nastąpić < to, czy jest to szablon, więc wyszukiwanie odbywa się w celu sprawdzenia, że ​​jest to szablon. Jeśli zostanie znaleziony szablon funkcji, wówczas < jest uważany za początek argumentów szablonu.

Po usunięciu zgłoszenia do przodu żaden szablon nie zostanie znaleziony, a operator<< zostanie uznany za obiekt. (To jest powód, dla którego po dodaniu using namespace std kod nadal się kompiluje, ponieważ muszą być deklaracje szablonów dla operator<<).

... po usunięciu deklaracji forward i użyciu alternatywnej deklaracji znajomego w powyższym kodzie. Zauważ, że parametr szablonu U nie pojawia się w następującym sygnaturze ...

Nie ma wymogu, aby wszystkie parametry szablonu były używane w argumentach szablonu funkcji. Alternatywna deklaracja dotyczy nowego szablonu funkcji, który będzie możliwy do wywołania tylko wtedy, gdy zostanie zadeklarowany w przestrzeni nazw i podany zostanie jawny argument szablonu.

Prostym przykładem może być:?

class A {}; 
template <typename T> A & operator<<(A &, int); 

void foo() { 
    A a; 
    operator<< <int> (a, 10); 
} 

... jest ten kod rzeczywiście poprawna ..

Dobrze istnieją dwie części do tego. Pierwszym jest to, że alternatywą funkcja przyjaciel nie odnosi się do deklaracji później w zakresie:

template <typename T> 
class test { 
    template <typename U> 
    friend std::ostream& operator<<(std::ostream &out, const test<T> &t); 
    }; 

template <typename T> 
std::ostream& operator<<(std::ostream &out, const test<T> &t); // NOT FRIEND! 

Funkcja przyjaciel faktycznie być zadeklarowane w przestrzeni nazw dla każdej specjalizacji:

template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<int> &t); 
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<char> &t); 
template <typename U> 
std::ostream& operator<<(std::ostream &out, const test<float> &t); 

Każda specjalizacja operator<< <U> będzie miał dostęp do określonej specjalizacji zgodnie z typem jej parametru test<T>. Zasadniczo dostęp jest ograniczony zgodnie z wymaganiami. Jednak jak już wspomniano wcześniej funkcje te są w zasadzie bezużyteczne jako operatorów, ponieważ trzeba użyć wywołania funkcji składni:

int main() 
{ 
    test<int> t; 
    operator<< <int> (std << cout, t); 
    operator<< <float> (std << cout, t); 
    operator<< <char> (std << cout, t); 
} 

Zgodnie z odpowiedziami na poprzednie pytanie, należy użyć deklarację przodu jak sugeruje litb, albo idź z definiowaniem funkcji przyjaciół w linii, zgodnie z odpowiedzią Dr_Asik's (co prawdopodobnie byłoby tym, co zrobiłbym).

UPDATE: 2nd Komentarz

... zmianę oświadczenia w postępowaniu przed klasą; jeden w klasie nadal pasuje do funkcji, która wdrożyć później ...

Jak już wskazano powyżej, kompilator sprawdza, czy operator<< jest szablonem kiedy ujrzy < w deklaracji:

friend std::ostream& operator<< < 

Czyni to, sprawdzając nazwę i sprawdzając, czy jest to szablon.Tak długo, jak masz deklarację typu "dummy forward", to "sztuczki" traktują kompilator traktując twojego przyjaciela jako nazwę szablonu, a więc < jest uważany za początek listy szablonów argumentów.

Później, gdy tworzysz instancję klasy, masz poprawny szablon do dopasowania. W istocie po prostu oszukujesz kompilator, traktując przyjaciela jako specjalizację szablonu.

Możesz to zrobić tutaj, ponieważ (jak wspomniałem wcześniej), w tym momencie nie ma miejsca semantyczne sprawdzenie.

+0

Początkowo myślałem, że działa to tak, jak opisałeś, ale tak nie jest (dlatego właśnie zadałem to pytanie). Kiedy powiedziałem "wszystko się kompiluje i działa poprawnie", faktycznie miałem na myśli, że udało mi się z powodzeniem utworzyć instancję klasy, jak również użyć operatora '<<' w zwykły sposób. Sprawdziłem również, czy funkcja operatora ma poprawny dostęp (np. Nie może uzyskać dostępu do prywatnych członków "testu ", jeśli został wywołany, aby wydrukować 'test '. Tak jest w przypadku kodu jak napisano, z "Błędna" deklaracja z wyprzedzeniem oraz z alternatywną deklaracją przyjaciela –

+0

Zdałem sobie również sprawę, że prawdopodobnie źle zrozumiałeś to, co powiedziałem o "fałszywej" deklaracji - mówię tylko o zmianie przedłożonej deklaracji przed klasą; klasa nadal pasuje do funkcji, którą później zaimplementuję (tak, że w chwili utworzenia potrzebny szablon funkcji został już przeanalizowany) –

+0

@Sumudu: Dodałem zaktualizowaną wersję, aby zaadresować twój drugi komentarz. Czy na początek możesz zmodyfikować swój pytanie, aby dołączyć przykład, który mówisz "kompiluje i działa poprawnie", ale używa szablonu alternatywnej funkcji znajomego? Kiedy robię to tutaj, otrzymuję błędy dostępu do członków w szablonie przestrzeni nazw. –

Powiązane problemy