不同编译器之间的名称查找不一致

Inconsistency in name lookup among different compilers

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

考虑以下程序:

#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+ 放入命名空间 N 中,它会起作用。您通常应该这样做,因为这样 ADL 可以帮助您解析运算符。

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

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

1) For arguments of fundamental type, the associated set of namespaces and classes is empty

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

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

就像 John Zwinck 说的,把运算符放到 namespace N 中。原因是 ADL 仅考虑相关 class 的最内层封闭命名空间。

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

For each argument type T in the function call, there is a set of zero or more associated namespaces and a set of zero or more associated classes to be considered. The sets of namespaces and classes are determined entirely by the types of the function arguments (and the namespace of any template template argument). Typedef names and using-declarations used to specify the types do not contribute to this set. The sets of namespaces and classes are determined in the following way:

  • [...]
  • If T is a class type (including unions), its associated classes are: the class itself; the class of which it is a member, if any; and its direct and indirect base classes. Its associated namespaces are the innermost enclosing namespaces of its associated classes. Furthermore, if T is a class template specialization, its associated namespaces and classes also include: the namespaces and classes associated with the types of the template arguments provided for template type parameters (excluding template template parameters); the namespaces of which any template template arguments are members; and the classes of which any member templates used as template template arguments are members. [ Note: Non-type template arguments do not contribute to the set of associated namespaces. — end note ]

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

If an associated namespace is an inline namespace, its enclosing namespace is also included in the set. If an associated namespace directly contains inline namespaces, those inline namespaces are also included in the set.

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