为什么重载的功能模板顺序对于基本类型很重要?

时间:2018-07-18 22:41:12

标签: c++ templates c++17 overloading

出于说明目的,请说我有这个设计好的容器:

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_toothis_blows_up都可以成功编译。

(这种行为也发生在STL容器中,与const和引用限定符无关)

为什么对于visit(Container<int>)不适用的订单,它对专门化visit(Container<Value>)变得重要?

在我的研究中,我怀疑这与ADL有关(我承认我并不完全了解)。但是我对此的最佳解释是,在Container<int>情况下的搜索结束时,由于普通的unqualified lookup,该集合应该已经找到int。因此,Container<int>Container<Value>都应该起作用。

Godbolt demonstrating this

我已经确认,尽管进行了重新排序,但仍会调用正确的重载(即,当visit(T)首先出现时,实际上visit(Container<T>)会调用visit(Container<int>),而不仅仅是选择更通用的{ {1}})。我相信这是因为选择了最具体的专业。

1 个答案:

答案 0 :(得分:5)

  

在我的研究中,我怀疑这必须处理ADL(我承认我并不完全了解)。

您是对的,这就是问题所在。

  

但我对此的最佳解释是,在Container<int>情况下的搜索结束时,由于普通的不合格查找,该集合应该已经找到int

普通的不合格查询无法看到template <typename T> void visit(const T &v)重载,因为尚未声明它。依赖参数的查找是一个例外。它可以在实例化时看到所有声明。

类型int是与任何特定名称空间无关的内置类型。由于int没有任何关联的名称空间,因此ADL不会在其中搜索声明的名称空间,因此无法找到第二个声明。

类型Value是全局名称空间中用户定义的类型。因此,ADL在全局名称空间中搜索声明,在这种情况下,它确实找到了第二个声明。