Ако кодът наистина е като това, което сте публикували, това е проблем с компилатора. Кодът се компилира добре в clang++, както трябва.
Приятелските декларации са странни с това, че декларират функция, която има обхват на пространство от имена, но декларацията е налична само през ADL и дори тогава, само ако поне един от аргументите е от типа на класа, който има приятелската декларация. Освен ако няма декларация на ниво пространство от имена, функцията не е налична в обхвата на пространството от имена.
Тест 1 (функцията не е налична на ниво пространство от имена без изрична декларация):
namespace A {
struct B {
friend void f(); // [1]
};
// void f(); // [2]
}
void A::f() {} // [3]
В [1] добавяме декларация за приятел, която декларира void A::f()
като приятел на A::B
. Без допълнителната декларация в [2] на ниво пространство от имена, дефиницията в [3] няма да успее да се компилира, тъй като е извън пространството от имена A
, тази дефиниция не е също и самодекларация.
Изводът тук е, че тъй като функцията не е достъпна за търсене на ниво пространство от имена, а само чрез ADL на Matrix<T>
(за някакъв конкретен инстанциращ тип T
), компилаторът не може да намери това като съвпадение на размяна на две int
стойности.
В отговора си Джеси Гуд заявява, че всяка инстанция на Matrix и Matrix ще съдържа една и съща дефиниция на вашата функция за размяна, тъй като сте дефинирали функцията приятел вътре в шаблона Matrix, което е пълен абсурд.
Приятелска функция, която е дефинирана вътре в класа, ще декларира и дефинира функция на ниво пространство от имена и отново, декларацията ще бъде налична само вътре в класа и достъпна чрез ADL. Когато това се направи в шаблон, той ще дефинира нешаблонирана безплатна функция на ниво пространство от имена за всяко екземпляр на шаблона, който използва функцията. Тоест, ще генерира различни дефиниции. Обърнете внимание, че вътре в обхвата на шаблона на класа името на шаблона идентифицира специализацията, която се инстанцира, т.е. вътре в Matrix<T>
идентификаторът Matrix
не наименува шаблона, а едно инстанциране на шаблона.
Тест 2
namespace X {
template <typename T>
struct A {
friend void function( A ) {}
};
template <typename T>
void funcTion( A<T> ) {}
}
int main() {
using namespace X;
A<int> ai; function(ai); funcTion(ai);
A<double> ad; function(ad); funcTion(ad);
}
$ make test.cpp
$ nm test | grep func | c++filt
0000000100000e90 T void X::funcTion<double>(A<double>)
0000000100000e80 T void X::funcTion<int>(A<int>)
0000000100000e70 T X::function(A<double>)
0000000100000e60 T X::function(A<int>)
Резултатът от nm
е списъкът със символи, а c++filt
ще преведе повредените имена в еквивалента в синтаксиса на C++. Резултатът от програмата ясно показва, че X::funcTion
е шаблон, който е създаден за два типа, докато X::function
са две претоварени функции без шаблон. Отново: две функции без шаблон.
Твърдението, че ще генерира същата функция, няма много смисъл, помислете, че има извикване на функция, да речем std::cout << lhs
, кодът трябва да избере правилното претоварване на operator<<
за текущото инстанциране на функцията. Няма нито един operator<<
, който може да приеме да речем int
и unsigned long
или std::vector<double>
(Нищо не ви пречи да създадете шаблона с всеки тип.
Отговорът от ildjarn предлага алтернатива, но не дава обяснение на поведението. Алтернативата работи, защото using-директива е напълно различна от using-declaration. По-специално, първото (using namespace X;
) променя търсенето, така че идентификаторите в пространството от имена X да са достъпни на ниво пространство от имена в едно от обграждащите пространства от имена на текущата част от кода (ако изградите дърво от пространства от имена, декларациите биха били налични там, където клонът, съдържащ X
, отговаря на клона, съдържащ кода, където се използва using-директивата).
От друга страна, using-declaration (using std::swap;
) осигурява using-declaration на функцията std::swap
в контекста, където присъства using-declaration . Ето защо трябва да използвате using-declarations, а не using-directives, за да приложите вашите swap функции:
Тест 3
namespace Y { struct Z {}; void swap( Z&,Z& ); }
namespace X {
struct A { int a; Y::Z b; };
void swap( A& lhs, A& rhs ) {
//using namespace std; // [1]
using std::swap; // [2]
swap( lhs.a, rhs.a ); // [3]
swap( lhs.b, rhs.b ); // [4]
}
}
Ако бяхме използвали using-директива [1], символите от пространството от имена ::std
щяха да бъдат достъпни за търсения, извършени във функцията ::X::swap(X::A&,X::A&)
, сякаш бяха декларирани в ::
(което е общият предшественик на ::std
и ::X
). Сега суапът в [3] няма да намери никаква swap
функция чрез ADL, така че ще започне да търси swap
функции в обхващащото пространство от имена. Първото обхващащо пространство от имена е X
и то съдържа функция swap
, така че търсенето ще спре и ще се активира разрешаването на претоварване, но X::swap
не е валидно претоварване за swap(int&,int&)
, така че няма да успее да се компилира.
Чрез използване на using-declaration ние привеждаме std::swap
декларация в обхвата на X::swap
(вътре във функцията!). Отново ADL няма да бъде приложен в [3] и търсенето ще започне. В текущия обхват (вътре във функцията) той ще намери декларацията на std::swap
и ще инстанцира шаблона. В [4] ADL се включва и ще търси функция swap
, дефинирана вътре във функцията ::Y::Z
и/или в ::Y
, и ще добави това към набора от претоварвания, намерени в текущия обхват ( отново, ::std::swap
). В този момент ::Z::swap
е по-добро съвпадение от std::swap
(предвид перфектно съвпадение, функция без шаблон е по-добро съвпадение от шаблонна) и получавате очакваното поведение.
person
David Rodríguez - dribeas
schedule
20.07.2012
swap
сMatrix<>
аргумента? Вашият пример използваint
s... Грешката прави да изглежда, че се опитвате да размените два различни типаMatrix<>
обекти (т.е. с два различниT
s вMatrix<T>
) -- защо това трябва да работи? - person ildjarn   schedule 20.07.2012