【问题标题】:Inconsistency in name lookup among different compilers不同编译器之间的名称查找不一致
【发布时间】:2018-02-08 12:22:57
【问题描述】:

首先,请随意为这个问题提出更好的标题。

考虑以下程序:

#include <numeric>
namespace N { class C {}; }
int operator+( int i, N::C ) { return i+1; }
int main() {
     N::C a[10];
     std::accumulate( a, a+10, 0 );
} 

g++5.4.0:编译成功(见现场演示here

clang++3.8.0(见现场演示here

错误:

In file included from source_file.cpp:3:
/usr/include/c++/v1/numeric:75:25: error: invalid operands to binary expression ('int' and 'N::C')
        __init = __init + *__first;
                 ~~~~~~ ^ ~~~~~~~~
source_file.cpp:8:11: note: in instantiation of function template specialization 'std::__1::accumulate<N::C *, int>' requested here
     std::accumulate( a, a+10, 0 );
          ^
/usr/include/c++/v1/iterator:640:1: note: candidate template ignored: could not match 'reverse_iterator<type-parameter-0-0>' against 'N::C'
operator+(typename reverse_iterator<_Iter>::difference_type __n, const reverse_iterator<_Iter>& __x)
^
/usr/include/c++/v1/iterator:1044:1: note: candidate template ignored: could not match 'move_iterator<type-parameter-0-0>' against 'N::C'
operator+(typename move_iterator<_Iter>::difference_type __n, const move_iterator<_Iter>& __x)
^
/usr/include/c++/v1/iterator:1400:1: note: candidate template ignored: could not match '__wrap_iter<type-parameter-0-0>' against 'N::C'
operator+(typename __wrap_iter<_Iter>::difference_type __n,
^
1 error generated.

Microsoft Visual C++19.00.23506(见现场演示here

错误:

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(20): error C2672: 'operator __surrogate_func': no matching overloaded function found
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(30): note: see reference to function template instantiation '_Ty std::_Accumulate<_Iter,_Ty,_Fn2>(_InIt,_InIt,_Ty,_Fn2)' being compiled
        with
        [
            _Ty=int,
            _Iter=N::C *,
            _Fn2=std::plus<void>,
            _InIt=N::C *
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(38): note: see reference to function template instantiation '_Ty std::accumulate<_InIt,_Ty,std::plus<void>>(_InIt,_InIt,_Ty,_Fn2)' being compiled
        with
        [
            _Ty=int,
            _InIt=N::C *,
            _Fn2=std::plus<void>
        ]
source_file.cpp(8): note: see reference to function template instantiation '_Ty std::accumulate<N::C*,int>(_InIt,_InIt,_Ty)' being compiled
        with
        [
            _Ty=int,
            _InIt=N::C *
        ]
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(20): error C2893: Failed to specialize function template 'unknown-type std::plus<void>::operator ()(_Ty1 &&,_Ty2 &&) const'
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(20): note: With the following template arguments:
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(20): note: '_Ty1=int &'
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\INCLUDE\numeric(20): note: '_Ty2=N::C &'

    Error(s):
    In file included from source_file.cpp:3:
    /usr/include/c++/v1/numeric:75:25: error: invalid operands to binary expression ('int' and 'N::C')
            __init = __init + *__first;
                     ~~~~~~ ^ ~~~~~~~~
    source_file.cpp:8:11: note: in instantiation of function template specialization 'std::__1::accumulate<N::C *, int>' requested here
         std::accumulate( a, a+10, 0 );
              ^
    /usr/include/c++/v1/iterator:640:1: note: candidate template ignored: could not match 'reverse_iterator<type-parameter-0-0>' against 'N::C'
    operator+(typename reverse_iterator<_Iter>::difference_type __n, const reverse_iterator<_Iter>& __x)
    ^
    /usr/include/c++/v1/iterator:1044:1: note: candidate template ignored: could not match 'move_iterator<type-parameter-0-0>' against 'N::C'
    operator+(typename move_iterator<_Iter>::difference_type __n, const move_iterator<_Iter>& __x)
    ^
    /usr/include/c++/v1/iterator:1400:1: note: candidate template ignored: could not match '__wrap_iter<type-parameter-0-0>' against 'N::C'
    operator+(typename __wrap_iter<_Iter>::difference_type __n,
    ^
    1 error generated.

这个程序在英特尔 C++ 编译器上编译也没有任何错误。

那么,问题是这里有哪些编译器?这段代码格式不正确吗?标准对此有何规定?

【问题讨论】:

  • 是的,如果我输入 operator+ into namespace N`,它就可以在 clang 上运行`
  • 众所周知,Visual Studio 编译器不实现两阶段查找(但它应该实现)。
  • 投反对票的人愿意解释为什么你投反对票的原因?
  • @JesperJuhl:但我没有在我的代码中使用用户定义的函数模板,那么如何在这里应用 2 阶段名称查找?

标签: c++ operator-overloading language-lawyer overload-resolution name-lookup


【解决方案1】:

就像 John Zwinck 所说,将运算符放入 namespace N。原因是 ADL 只考虑相关类的最内层命名空间。

来自[basic.lookup.argdep]/2,强调我的:

对于函数调用中的每个参数类型 T,都有一组零 或多个关联的命名空间和一组零个或多个关联的 要考虑的类。命名空间和类的集合是 完全由函数参数的类型(以及 任何模板模板参数的命名空间)。 Typedef 名称和 用于指定类型的 using-declarations 对此没有贡献 放。命名空间和类的集合在 以下方式:

  • [...]
  • 如果 T 是类类型(包括联合),则其关联的类是:类本身;它所属的类别(如有的话);及其 直接和间接基类。 其关联的命名空间是 其关联类的最内层封闭命名空间。 此外,如果 T 是类模板特化,则其相关联的 命名空间和类还包括:命名空间和类 与提供的模板参数的类型相关联 模板类型参数(不包括模板模板参数);这 任何模板模板参数都是其成员的命名空间;和 任何成员模板用作模板模板的类 参数是成员。 [ 注意:非类型模板参数不 为一组关联的命名空间做出贡献。 — 尾注 ]

如果该命名空间是内联命名空间,则只有一个特殊例外。

如果关联的命名空间是内联命名空间,则其封闭 命名空间也包含在集合中。如果关联的命名空间 直接包含内联命名空间,那些内联命名空间也是 包含在集合中。

因此,ADL 不应找到您的 operator+,因此不应参与 std::accumulate 内的重载决议。

【讨论】:

  • @Destructor - 我不知道。我还没有投票,我正忙着写这篇文章
  • 那么,这是 G++ 和 Intel 编译器中的错误吗?
  • @Destructor - 如果您寻求严格的标准合规性,我会说是的。
  • 这不是 GCC 或 ICC 中的错误。那些编译器可以拒绝编译代码,或者他们可以编译代码。该代码在技术上无效,但编译器不需要拒绝它。
  • @JohnZwinck:规范是否明确指出这是不正确的,不需要诊断?
【解决方案2】:

如果将 operator+ 放入命名空间 N 就可以了。通常应该这样做,因为这样 ADL 可以帮助您解析运算符。

Clang 5 甚至明确告诉您应该这样做。

您可能认为 ADL 将导致在全局命名空间中查找,因为其中一个参数是 int。但事实并非如此,因为:

1) 对于基本类型的参数,相关的命名空间和类集是空的

也就是说,int 这样的类型不会导致 ADL 在全局命名空间中查找您的运算符。

请看这里:http://en.cppreference.com/w/cpp/language/adl

【讨论】:

  • 这并不能回答哪个编译器在这里的问题
  • @Destructor:代码与最初编写的一样无效。所有编译器都是正确的:有些编译器比其他编译器更严格(也就是功能或适应性较差)。
  • 提供标准引用
  • @Destructor:我添加了引用相关部分的引用。
猜你喜欢
  • 1970-01-01
  • 2015-10-08
  • 2016-07-12
  • 1970-01-01
  • 2018-05-09
  • 2016-12-21
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
相关资源
最近更新 更多