如果代码真的像您发布的那样,那是编译器的问题。代码在 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 on Matrix<T>
(对于某些特定的实例化类型T
),编译器不可能将其与两个int
值的交换匹配。
在他的回答中,Jesse Good 指出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<<
可以说 aint
和 an unsigned long
or std::vector<double>
(没有什么能阻止你用任何类型实例化模板。
ildjarn 的答案提出了一个替代方案,但没有提供对该行为的解释。替代方法有效,因为using-directive与using-declaration完全不同。特别是,前者using namespace X;
(包含满足包含使用 using 指令的代码的分支)。X
另一方面,using-declaration ( using std::swap;
)在存在using-declaration的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 指令[1],则命名空间中的符号::std
将可用于在函数内部执行的查找,::X::swap(X::A&,X::A&)
就好像它们已在中声明::
(这是::std
and的共同祖先::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
和/或 in 中定义的函数::Y
,并将其添加到当前作用域中找到的重载集(再次,::std::swap
)。在这一点上,::Z::swap
是一个更好的匹配std::swap
(给定一个完美的匹配,一个非模板化的函数比一个模板化的更好)并且你得到了预期的行为。