Если код действительно похож на то, что вы опубликовали, это проблема с компилятором. Код отлично компилируется в 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 namespace X;
) модифицирует поиск, чтобы идентификаторы в пространстве имен X были доступны на уровне пространства имен в одном из включающих пространств имен текущего фрагмента кода (если вы строите дерево пространств имен, объявления будут доступны там, где ветвь, содержащая X
, встречается с ветвью, содержащей код, в котором используется using-directive).
С другой стороны, декларация использования (using std::swap;
) предоставляет декларацию функции std::swap
в контексте, где присутствует декларация использования. . Вот почему вы должны использовать using-declarations, а не using-directives для реализации ваших функций подкачки:
Тест 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-directive [1], символы из пространства имен ::std
были бы доступны для поиска, выполняемого внутри функции ::X::swap(X::A&,X::A&)
, как если бы они были объявлены в ::
(который является общим предком ::std
и ::X
). Теперь подкачка в [3] не найдет ни одной swap
функции через ADL, поэтому она начнет поиск swap
функций в окружающем пространстве имен. Первое окружающее пространство имен — X
, и оно содержит функцию swap
, поэтому поиск остановится и включится разрешение перегрузки, но X::swap
не является допустимой перегрузкой для swap(int&,int&)
, поэтому оно не сможет скомпилироваться.
Используя объявление-использования, мы помещаем объявление 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