Do napisania tej notki zainspirowało mnie to pytanie na forum. Lekko parafrazując podany kod, szablon klasy wyglądał następująco:
template<typename T> void bar(Foo<T> f) { DBG(f.val); } template<typename T> struct Foo { Foo(T val) : val{val} {} template<typename U> friend void bar(Foo<U>); private: T val; }; |
Powyższy kod niepotrzebnie1 zaprzyjaźnia wszystkie specjalizacje bar. Na przykład, dla Foo<int>, zaprzyjaźniona zostanie zarówno bar<int>, jak i każda inna specjalizacja, chociażby bar<float>. W efekcie legalna będzie również taka specjalizacja:
template<> void bar<int>(Foo<int> x) { Foo<float> y{static_cast<float>(x.val)}; DBG(y.val); } |
W zdecydowanej większości przypadków nie ma to sensu, a zamierzeniem jest zaprzyjaźnienie wyłącznie specjalizacji po tym samym typie, po którym konkretyzowana jest klasa. W takim wypadku można zaprzyjaźnić konkretną specjalizację:
template<typename T> struct Foo { // ... friend void bar<T>(Foo<T>); // ... }; |
Lub, krócej, wykorzystując dedukcję typów i injected class name:
template<typename T> struct Foo { // ... friend void bar<>(Foo); // ... }; |
1Zakładając, że autor chciał zaprzyjaźnić wyłącznie bar<T>, ale nie wiedział jak. Tak było w przypadku wspomnianego wątku.
s/jest będzie/będzie/ 🙂
Dzięki, poprawione 🙂