[英]How does ADL affect this piece of C++ code?
实际上,下面的代码不能用 Clang 使用这个命令编译:
clang++ -std=c++11 test.cc -o test
。
我只想模仿与 C++ 中的“swapping idiom”相同的行为,以使用“using-directive”来启用 ADL。 但是下面的代码哪里错了? 预期的调用优先级应该是: N1::foo
> N2::foo
> ::foo
,对吧?
namespace N1 {
struct S {};
void foo(S s) {
std::cout << "called N1::foo.";
}
}
namespace N2 {
void foo(N1::S s) {
std::cout << "called N2::foo.";
}
}
void foo(N1::S s) {
std::cout << "called foo.";
}
int main() {
using N2::foo;
foo(N1::S{});
}
错误信息:
test.cc:54:3: error: call to 'foo' is ambiguous
foo(N1::S{});
^~~
test.cc:40:8: note: candidate function
void foo(S s) {
^
test.cc:45:8: note: candidate function
void foo(N1::S s) {
^
1 error generated.
更新:
我将 N2::foo 更改为可以在某种程度上模仿 std::swap 的模板方法。 所以,这里的问题是为什么::foo
不能被main
function中的“ foo(N1::S{});
”调用? 由于 function 应该比模板 function 在具有相同优先级时调用更合适。
namespace N1 {
struct S {};
/*
void foo(S s) {
std::cout << "called N1::foo, specific one." << '\n';
}
*/
}
namespace N2 { // as a fallback to unqualified name which has no user-defined overload.
template<typename T>
void foo(T) {
std::cout << "called N2::foo, generic one." << '\n';
}
}
void foo(N1::S s) {
std::cout << "called foo." << '\n';
}
int main() {
using N2::foo;
foo(N1::S{});
foo(10); // use generic version.
}
在这种情况下, 正常的名称查找找到N2::foo
, N1::foo
被ADL找到,它们都被添加到重载集,然后执行重载解析并且调用是不明确的。
顺便说一句:不using N2::foo;
在main()
中, ::foo
将通过正常名称查找找到,而N1::foo
也将通过 ADL 找到; 结果调用仍然不明确。
更新:
所以,这里的问题是为什么
::foo
不能被main
function中的“foo(N1::S{});
”调用?
因为using N2::foo;
, 名称N2::foo
在main
function 中引入。当调用foo
时,名称N2::foo
将在main
的 scope 中找到,然后名称查找停止,进一步的 scope(全局命名空间)将不会被检查,因此根本不会找到::foo
并将其添加到重载集中。 结果N2::foo
在这两种情况下都会被调用。
名称查找按如下所述检查范围,直到它找到至少一个任何类型的声明,此时查找停止并且不再检查其他范围。
顺便说一句:如果你using N2::foo;
在main
之前的全局命名空间中, foo(N1::S{});
会打电话给::foo
。 N2::foo
和::foo
都是通过名称查找找到的,并且::foo
在重载解析中获胜。
首先,你有普通的查找,从内部范围搜索到外部范围,并在第一次匹配时停止,从后面的范围隐藏重载。 然后,当触发 ADL 时,它将向搜索中添加额外的关联实体和名称空间。
因此,在您的情况下,ADL 查找不会向重载集添加任何内容。
#include <iostream>
namespace N1 {
struct S {};
/*
void foo(S s) {
std::cout << "called N1::foo, specific one." << '\n';
}
*/
}
namespace N2 {
template<typename T>
void foo(T) {
std::cout << "called N2::foo, generic one." << '\n';
}
}
void foo(N1::S s) {
std::cout << "called foo." << '\n';
}
int main() {
using N2::foo;
foo(N1::S{}); // ordinary lookup finds N2 in main scope and stops.
// adl lookup add N1 ns to the additionnal ns set but finds nothing
// overload set = N2::foo by ordinary lookup
}
现在,如果我取消注释你的 S1 版本,它会获胜:这就是你得到的:
#include <iostream>
namespace N1 {
struct S {};
void foo(S s) {
std::cout << "called N1::foo, specific one." << '\n';
}
}
namespace N2 {
template<typename T>
void foo(T) {
std::cout << "called N2::foo, generic one." << '\n';
}
}
void foo(N1::S s) {
std::cout << "called foo." << '\n';
}
int main() {
using N2::foo;
foo(N1::S{}); // ordinary lookup finds N2 in main scope and stops
// adl lookup add N1 ns to the additionnal ns set and finds N1::foo
// overload set = N2::foo by ordinary lookup, N1::foo by ADL, N1::foo wins
}
你用交换得到同样的东西。 在这种情况下,使用 Foo::swap 是因为它位于 N1 命名空间中:
#include <iostream>
namespace N1 {
struct Foo {
};
void swap(Foo& , Foo&) {
std::cout << "swap Foo" << std::endl;
}
}
namespace N2 {
struct S {
N1::Foo f;
};
void swap(S& l,S& r) {
using std::swap; // overload set is std::swap by ordinary lookup
swap(l.f, r.f); // and Foo::swap by ADL, Foo::swap wins
}
}
int main() {
N2::S s1,s2;
swap(s1,s2);
}
但是,如果您在全局 ns 中移动特定于 Foo 的交换,则会调用std::swap
:
#include <iostream>
namespace N1 {
struct Foo {
};
}
void swap(N1::Foo& , N1::Foo&) {
std::cout << "swap Foo" << std::endl;
}
namespace N2 {
struct S {
N1::Foo f;
};
void swap(S& l,S& r) {
using std::swap; // overload set is std::swap by ordinary lookup
swap(l.f, r.f); // because ADL does not add the global ns to the
// ns to be searched for
}
}
int main() {
N2::S s1,s2;
swap(s1,s2);
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.