Почему ADL имеет приоритет над функцией в пространстве имен std, но приравнивается к функции в пространстве имен, определяемом пользователем?

У меня есть два фрагмента для ADL в демонстрационных целях. Оба фрагмента были скомпилированы компиляторами VC10, gcc и comeau C ++, и результат одинаков для всех трех.

‹1> ADL против использования директивы пользовательского пространства имен:

#include <algorithm>
namespace N 
{ 
    struct T {}; 
    void swap(T,T) {} 
} 

namespace M 
{ 
    void swap(N::T,N::T) {} 
} 

int main() 
{ 
    using M::swap; 
    N::T o1,o2;
    swap(o1,o2); 
}

Результат компиляции:

error C2668: 'M::swap' : ambiguous call to overloaded function
could be 'void M::swap(N::T,N::T)'
or       'void N::swap(N::T,N::T)' [found using argument-dependent lookup]

Это ожидается, поскольку ADL не имеет приоритета над обычным результатом поиска, плюс ADL не является гражданином 2-го класса, результат поиска ADL объединяется с обычным (не ADL) неквалифицированным поиском. Вот почему у нас есть двусмысленность.

‹2> ADL против использования директивы пространства имен std:

#include <algorithm>
namespace N 
{ 
    struct T {}; 
    void swap(T,T) {}  //point 1
} 

namespace M 
{ 
    void swap(N::T,N::T) {} 
} 

int main() 
{ 
    using std::swap; 
    N::T o1,o2;
    swap(o1,o2); 
}

Этот компилируется нормально.

В результате компилятор выбирает результат ADL (имеет прецедент std :: swap), что означает, что будет вызываться N::swap() в «точке 1». Только когда в отсутствие «точки 1» (скажем, если я закомментирую эту строку), компиляция вместо этого будет использовать откат std::swap.

Обратите внимание, что этот способ использовался во многих местах как способ перезаписать std::swap. Но у меня вопрос: почему ADL имеет приоритет над «пространством имен std» (case2), но считается равным определяемой пользователем функции пространства имен (case1)?

Есть ли в стандарте C ++ абзац, в котором говорится об этом?

================================================== =============================== Редактировать после прочтения полезных ответов, может быть полезно другим.

Итак, я настроил свой фрагмент 1, и теперь двусмысленность исчезла, и при компиляции явно предпочитаю функцию Nontemplate при разрешении перегрузки!

#include <algorithm>
namespace N 
{ 
    struct T {}; 
    void swap(T,T) {} 
} 

namespace M 
{ 
    template<class T>
    void swap(N::T,N::T) {} 
} 

int main() 
{ 
    using M::swap;
    N::T o1,o2;
    swap(o1,o2); //here compiler choose N::swap()
}

Я также изменил свой фрагмент 2. Просто для развлечения!

#include <algorithm>
namespace N 
{ 
    struct T {}; 

    template<class _Ty> inline
    void swap(_Ty& _Left, _Ty& _Right)
    {
        _Ty _Tmp = _Move(_Left);
        _Left = _Move(_Right);
        _Right = _Move(_Tmp);
    }
} 

namespace M 
{ 
    void swap(N::T,N::T) {} 
} 

int main() 
{ 
    using std::swap; 
    N::T o1,o2;
    swap(o1,o2); 
}

gcc и comeau, как и ожидалось, говорят о двусмысленности:

"std::swap" matches the argument list, the choices that match are:
            function template "void N::swap(_Ty &, _Ty &)"
            function template "void std::swap(_Tp &, _Tp &)"

Кстати, VC10, как обычно, глупо, пусть это пройдет нормально, если я не удалю 'using std :: swap'.

Еще немного, чтобы написать: перегрузка C ++ может быть сложной (30+ страниц в стандарте C ++), но в приложении B есть очень удобочитаемая страница из 10 ...

Спасибо за хороший вклад, теперь все ясно.


person Gob00st    schedule 05.10.2012    source источник


Ответы (2)


Ваш тест не проверяет, имеет ли ADL приоритет над обычным поиском, а проверяет, как разрешение перегрузки определяет наилучшее совпадение. Причина того, что второй тестовый пример работает, заключается в том, что std::swap является шаблоном, и при выполнении разрешения перегрузки для идеального совпадения (найденного ADL) и шаблона, функция без шаблона имеет приоритет.

person David Rodríguez - dribeas    schedule 05.10.2012
comment
Примечательно, что ADL относится к поиску имени, а поиск имени не имеет понятия приоритета. - person Kerrek SB; 05.10.2012
comment
@KerrekSB: Я думаю, что Дэвид говорил о стадии поиска имени в разрешении перегрузки, что касается выбора наилучшего совпадения. - person Gob00st; 05.10.2012

Вызов функции происходит в несколько этапов :

  1. name lookup -> puts candidate functions in a so-called overload set
    • this is the part where ADL happens if you have an unqualified name lookup
  2. вывод аргументов шаблона -> для каждого шаблона в наборе перегрузки
  3. разрешение перегрузки -> выберите лучшее совпадение

Вы путаете часть 1 с частью 3. Поиск имени фактически помещает обе swap функции в набор перегрузки ({N::swap, std::swap}), но часть 3 решает, какую из них вызывать в конце.

Теперь, поскольку std::swap является шаблоном, а в стандарте говорится, что нешаблонные функции более специализированы, чем функции шаблона при разрешении перегрузки, ваш <2> вызывает N::swap:

§13.3.3 [over.match.best] p1

С учетом этих определений жизнеспособная функция F1 определяется как лучшая функция, чем другая жизнеспособная функция F2, если [...]

  • F1 - это функция, не являющаяся шаблоном, а F2 - это специализация шаблона функции [...]

† Я рекомендую первые три видео из этой прекрасной серии на сайте тема.

person Xeo    schedule 05.10.2012
comment
Спасибо за стандартную ссылку! - person Gob00st; 05.10.2012
comment
@Xeo: не знаю (относительно отрицательного голоса), первое предложение меня немного смутило, потому что компилятор не находит всех функций, которые могут быть вызваны. Поиск имени - это поиск только подмножества. - person Matthieu M.; 05.10.2012
comment
@Matthieu: Я как бы чрезмерно обобщил это. Исправлено и улучшено. - person Xeo; 05.10.2012