Несоответствие в поиске имени среди разных компиляторов

Прежде всего, не стесняйтесь предлагать лучшее название для этого вопроса.

Рассмотрим следующую программу:

#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: компиляция прошла успешно (см. живую демонстрацию здесь)

clang++ 3.8.0 (см. живую демонстрацию здесь)

Ошибки:

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 (см. живую демонстрацию здесь)

Ошибки:

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.

Эта программа на удивление компилируется без ошибок и на компиляторе Intel C++.

Итак, вопрос в том, какие компиляторы здесь правильные? Этот код плохо сформирован? Что стандарт говорит об этом?


person Destructor    schedule 08.02.2018    source источник
comment
Да, это работает на clang, если я поставлю operator+ into namespace N`   -  person Destructor    schedule 08.02.2018
comment
Известно, что компилятор Visual Studio не реализует двухэтапный поиск (но должен).   -  person Jesper Juhl    schedule 08.02.2018
comment
Downvoter не хочет объяснить, почему этот вопрос вы проголосовали?   -  person Destructor    schedule 08.02.2018
comment
@JesperJuhl: Но я не использовал определяемый пользователем шаблон функции в своем коде, так как здесь будет применяться двухфазный поиск имени?   -  person Destructor    schedule 08.02.2018


Ответы (2)


Как сказал Джон Цвинк, поместите оператор в namespace N. Причина в том, что ADL рассматривает только самое внутреннее объемлющее пространство имен рассматриваемого класса.

Из [basic.lookup.argdep]/2 , выделение мое:

Для каждого типа аргумента T в вызове функции существует набор из нуля или более связанных пространств имен и набор из нуля или более связанных классов, которые необходимо учитывать. Наборы пространств имен и классов полностью определяются типами аргументов функции (и пространством имен любого аргумента шаблона шаблона). Имена typedef и объявления using, используемые для указания типов, не входят в этот набор. Наборы пространств имен и классов определяются следующим образом:

  • [...]
  • Если T является типом класса (включая объединения), его ассоциированными классами являются: сам класс; класс, членом которого он является, если таковой имеется; и его прямые и косвенные базовые классы. Связанные с ним пространства имен являются самыми внутренними объемлющими пространствами имен связанных с ним классов. Кроме того, если T является специализацией шаблона класса, связанные с ним пространства имен и классы также включают: пространства имен и классы, связанные с типами аргументов шаблона, предоставляемых для параметров типа шаблона (исключая параметры шаблона шаблона); пространства имен, членами которых являются любые аргументы шаблона шаблона; и классы, членами которых являются любые шаблоны-члены, используемые в качестве аргументов шаблона шаблона. [ Примечание. Аргументы шаблона, не относящиеся к типу, не влияют на набор связанных пространств имен. — конец примечания ]

За исключением специального исключения, если это пространство имен является встроенным пространством имен.

Если связанное пространство имен является встроенным пространством имен, окружающее его пространство имен также включается в набор. Если связанное пространство имен непосредственно содержит встроенные пространства имен, эти встроенные пространства имен также включаются в набор.

Таким образом, ваш operator+ не должен быть найден ADL, и поэтому не должен участвовать в разрешении перегрузки внутри std::accumulate.

person StoryTeller - Unslander Monica    schedule 08.02.2018
comment
@Destructor - я бы не знал. Я еще не проголосовал за это, я был занят написанием этого - person StoryTeller - Unslander Monica; 08.02.2018
comment
Итак, это ошибка в компиляторе G++ и Intel? - person Destructor; 08.02.2018
comment
@Destructor - Если вы ищете строгое соответствие стандартам, я бы сказал да. - person StoryTeller - Unslander Monica; 08.02.2018
comment
Это не ошибка в GCC или ICC. Эти компиляторы могли отказаться от компиляции кода, или они могли скомпилировать код. Код технически недействителен, но компиляторы не обязаны его отвергать. - person John Zwinck; 08.02.2018
comment
@JohnZwinck: В спецификации прямо сказано, что это неправильно, диагностика не требуется? - person Destructor; 08.02.2018
comment
@Destructor - Неспособность выбрать перегрузку делает программу неправильной с требуемой диагностикой. Теперь сам стандарт говорит вам, какие функции должны появиться в наборе перегрузки. Джон, похоже, имеет в виду, что это не исключает появления дополнительных функций в наборе. Хотя я бы сказал, что это все же не функция, а действительно ошибка. - person StoryTeller - Unslander Monica; 08.02.2018

Это работает, если вы поместите оператор + в пространство имен N. Обычно вы должны делать это, потому что тогда ADL может помочь вам разрешить оператор.

Clang 5 даже прямо говорит вам, что вы должны это сделать.

Вы можете подумать, что ADL приведет к поиску в глобальном пространстве имен, потому что один из аргументов — int. Но это не так, потому что:

1) Для аргументов фундаментального типа связанный набор пространств имен и классов пуст.

То есть это не тот случай, когда тип, подобный int, заставит ADL искать ваш оператор в глобальном пространстве имен.

См. здесь: http://en.cppreference.com/w/cpp/language/adl

person John Zwinck    schedule 08.02.2018
comment
Это не отвечает на вопрос, какой компилятор здесь - person Destructor; 08.02.2018
comment
@Destructor: код недействителен в первоначальном виде. Все компиляторы правы: некоторые просто более строгие (то есть менее функциональные или уступчивые), чем другие. - person John Zwinck; 08.02.2018
comment
предоставить стандартную цитату - person Destructor; 08.02.2018
comment
@Destructor: я добавил ссылку с цитатой соответствующей части. - person John Zwinck; 08.02.2018