出于说明目的,请说我有这个设计好的容器:
template <typename T>
struct Container {
T v;
};
这是人为设计的“值:”
struct Value {};
我有一个完全像这样声明的递归模板函数:
template <typename T>
void visit(const Container<T> &c) {
visit(c.v);
}
template <typename T>
void visit(const T &v) {}
使用上述方法,调用visit(Value{})
是完全有效的。我还可以调用:
void this_works_too(Container<Value> c) {
visit(c);
}
这有点令人困惑,因为递归调用visit(c.v)
早于其定义。
但是,当我们尝试以下操作时,会出现错误(来自clang 6和gcc 8):
void this_blows_up(Container<int> c) {
visit(c);
}
它现在抱怨递归调用visit(c.v)
:
error: call to function 'visit' that is neither visible in the
template definition nor found by argument dependent lookup
但是,如果我们重新排序visit
的声明:
template <typename T>
void visit(const T &c) {}
template <typename T>
void visit(const Container<T> &c) {
visit(c.v);
}
this_works_too
和this_blows_up
都可以成功编译。
(这种行为也发生在STL容器中,与const
和引用限定符无关)
为什么对于visit(Container<int>)
不适用的订单,它对专门化visit(Container<Value>)
变得重要?
在我的研究中,我怀疑这与ADL有关(我承认我并不完全了解)。但是我对此的最佳解释是,在Container<int>
情况下的搜索结束时,由于普通的unqualified lookup,该集合应该已经找到int
。因此,Container<int>
和Container<Value>
都应该起作用。
我已经确认,尽管进行了重新排序,但仍会调用正确的重载(即,当visit(T)
首先出现时,实际上visit(Container<T>)
会调用visit(Container<int>)
,而不仅仅是选择更通用的{ {1}})。我相信这是因为选择了最具体的专业。
答案 0 :(得分:5)
在我的研究中,我怀疑这必须处理ADL(我承认我并不完全了解)。
您是对的,这就是问题所在。
但我对此的最佳解释是,在
Container<int>
情况下的搜索结束时,由于普通的不合格查找,该集合应该已经找到int
。
普通的不合格查询无法看到template <typename T> void visit(const T &v)
重载,因为尚未声明它。依赖参数的查找是一个例外。它可以在实例化时看到所有声明。
类型int
是与任何特定名称空间无关的内置类型。由于int
没有任何关联的名称空间,因此ADL不会在其中搜索声明的名称空间,因此无法找到第二个声明。
类型Value
是全局名称空间中用户定义的类型。因此,ADL在全局名称空间中搜索声明,在这种情况下,它确实找到了第二个声明。