【问题标题】:Different behavior for qualified and unqualified name lookup for template模板的合格和不合格名称查找的不同行为
【发布时间】:2011-12-14 08:18:35
【问题描述】:

这段代码应该如何表现?如果我在 call_read() 函数中使用 qualified 名称,它会调用通用函数而忽略我的重载;如果我使用 unqualified 名称,它首先调用重载,然后调用通用版本。有什么不同?它是 GCC 中的错误吗?

#include <iostream>

struct info1 {};
struct info2 {};

template<class T> void read(T& x)
{
   std::cout << "generic" << std::endl;
}

template<class T> void call_read(T& x)
{
   ::read(x); // if I replace ::read(x) with read(x) the overload is called
}

void read(info1& x)
{
   std::cout << "overload" << std::endl;
}

int main()
{
   info1 x;
   info2 y;
   call_read(x);
   call_read(y);
}

我还注意到它对基本类型的工作方式不同。 请看下面的代码

#include <iostream>

typedef struct info1 {};
typedef struct info2 {};
typedef int info3;
typedef double info4;

template<class T> void read(T x)
{
    std::cout << "generic" << std::endl;
}

template<class T> void call_read(T x)
{
    read(x);
}

void read(info1 x)
{
    std::cout << "overload" << std::endl;
}
void read(info3 x)
{
    std::cout << "overload" << std::endl;
}

int main()
{
    call_read(info1());
    call_read(info2());
    call_read(info3());
    call_read(info4());
}

它应该调用两次重载函数,但事实并非如此。 在这里查看结果 http://codepad.org/iFOOFD52

【问题讨论】:

  • 看起来很奇怪!其他问题:如果call_read(T&amp;) 看不到read(info&amp;),怎么会被调用?
  • 我不是,只是为了测试目的。我看到它的行为基于它的调用方式而有所不同。这很有趣。
  • 我认为@iammilind 有所作为。如果您更改顺序并且read 的重载版本出现在call_read 之前,它将按预期工作。我不知道为什么没有:: 它确实会调用重载(这似乎是错误)。

标签: c++ templates lookup language-lawyer argument-dependent-lookup


【解决方案1】:

您所观察的是两阶段名称查找参数相关查找的叠加。

让我们看看标准是怎么说的 (C++03)。 [temp.dep]:

[...] 形式的表达式:

postfix-expression ( expression-listopt )

其中后缀表达式是一个标识符,该标识符表示一个依赖名称当且仅当表达式列表中的任何表达式是一个依赖于类型的表达式 (14.6.2.2)。

这意味着在read::read 中,read 是一个从属名称,因为x 是类型相关的。这意味着它在实例化时已解决。让我们看看这个[temp.dep.candidate]的规则是什么:

对于依赖于模板参数的函数调用,如果函数名称是非限定 ID 但不是模板 ID,则使用通常的查找规则(3.4.1、3.4.2)找到候选函数,除了那:

——对于使用非限定名称查找 (3.4.1) 的查找部分,仅找到具有来自模板定义上下文的外部链接的函数声明。

因此对于::read 情况,仅考虑在模板定义之前声明的函数。但是:

——对于使用关联命名空间(3.4.2)的部分查找,只有在模板定义上下文或模板实例化上下文。

对于不合格的read,两个函数都被考虑在内,在模板定义和模板实例化中可见。

【讨论】:

  • 我认为这不是真的。 x 始终是一个从属名称,它将查找延迟到第二阶段。不同之处在于一种语法会禁用 ADL,而另一种则不会。你不同意吗?
  • @sellibitze:我同意。但 ADL 与问题无关。我很确定这是一个编译器错误。
  • 我不同意你的分析。这不是编译器错误。这是预期的行为。
  • C++14 更新:::read 不再被视为 从属名称(即在第一阶段查找),但其他文本已调整为对此进行补偿;它仍然认为只有通过 3.4.2 查找的函数才会在实例化上下文中查找
【解决方案2】:

是的,这是预期的行为。在第一种情况 (::read) 中,您有效地禁用了 ADL(依赖于参数的查找),它将名称查找限制为在您使用 read 之前在全局范围内声明的内容。如果您删除 :: ADL 将启动,这可能会解析为您在函数模板之后声明的函数。

编辑:因为对于像 intdouble 这样的基本类型没有 ADL,这解释了你的第二个观察结果。

【讨论】:

  • +1,供您分析(如果是真的)。但另一方面,您也可以解释为什么 read(into1&amp;)call_read() 可见?由于 2 阶段参数查找?
  • @iammilind: info1 是一个类类型。因此,它有一个关联的命名空间,因此 ADL 开始发挥作用。正如我所说,ADL 不限于查找在使用之前已声明的实体。
  • 谢谢! Koenig 查找解释了 int 和结构之间的区别。
【解决方案3】:

编译器将始终调用与您的调用最匹配的方法。你在这里打电话:

read(T& x) [with T = info1]

因此编译器会更喜欢重载,因为它与调用精确匹配。它在模板特化的逻辑中,它允许说如果存在更匹配您的调用的重载函数,那么将使用这个。

对于问题的第二部分,关于使用完全限定名称和非限定名称时的区别,它来自这样一个事实,即完全限定名称不依赖于其他任何东西,因此被解析为第一个匹配项(这里你的模板声明)。

【讨论】:

  • 我的意思是应该为 info3 调用重载,但事实并非如此。这两个重载之间的唯一区别是类型。一个是基本的,另一个不是。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多