Подтвердить что ты не робот

Почему этот вызов для swap() неоднозначен?

Следующая программа

#include <algorithm>
#include <utility>
#include <memory>

namespace my_namespace
{


template<class T>
void swap(T& a, T& b)
{
  T tmp = std::move(a);
  a = std::move(b);
  b = std::move(tmp);
}

template<class T, class Alloc = std::allocator<T>>
class foo {};

}

int main()
{
  my_namespace::foo<int> *a, *b;

  using my_namespace::swap;

  swap(a,b);

  return 0;
}

вызывает как g++, так и clang выдачу следующей ошибки компилятора в моей системе:

$ clang -std=c++11 swap_repro.cpp -I.
swap_repro.cpp:28:3: error: call to 'swap' is ambiguous
  swap(a,b);
  ^~~~
/usr/bin/../lib/gcc/x86_64-linux-gnu/5.2.1/../../../../include/c++/5.2.1/bits/algorithmfwd.h:571:5: note: candidate function [with _Tp = my_namespace::foo<int, std::allocator<int> > *]
    swap(_Tp&, _Tp&)
    ^
swap_repro.cpp:10:6: note: candidate function [with T = my_namespace::foo<int, std::allocator<int> > *]
void swap(T& a, T& b)
     ^
1 error generated.

$ g++ -std=c++11 swap_repro.cpp -I.
swap_repro.cpp: In function ‘int main()’:
swap_repro.cpp:28:11: error: call of overloaded ‘swap(my_namespace::foo<int>*&, my_namespace::foo<int>*&)’ is ambiguous
   swap(a,b);
           ^
swap_repro.cpp:28:11: note: candidates are:
swap_repro.cpp:10:6: note: void my_namespace::swap(T&, T&) [with T = my_namespace::foo<int>*]
 void swap(T& a, T& b)
      ^
In file included from /usr/include/c++/4.9/bits/stl_pair.h:59:0,
                 from /usr/include/c++/4.9/utility:70,
                 from /usr/include/c++/4.9/algorithm:60,
                 from swap_repro.cpp:1:
/usr/include/c++/4.9/bits/move.h:166:5: note: void std::swap(_Tp&, _Tp&) [with _Tp = my_namespace::foo<int>*]
     swap(_Tp& __a, _Tp& __b)
     ^

Я не понимаю, почему std::swap рассматривается как перегрузка кандидата, но он имеет какое-то отношение к foo использованию std::allocator<T>.

Устранение второго параметра шаблона foo позволяет программе скомпилировать без ошибок.

4b9b3361

Ответ 1

Поскольку std::allocator<T> используется как аргумент типа шаблона, пространство имен std является ассоциированным пространством имен для ADL.

[basic.lookup.argdep]/2, bullet 2, emphasis mine:

Кроме того, если T является специализацией шаблона класса, пространства имен и классы также включают: пространства имен и классы связанные с типами аргументов шаблона, предоставленными для параметры типа шаблона (исключая параметры шаблона шаблона); пространства имен, членами которых являются аргументы шаблона шаблона; и классы, в которых любые шаблоны элементов, используемые в качестве шаблона аргументы шаблона являются членами.

... и указатели имеют одинаковый набор связанных пространств имен/классов в качестве типа, на который они указывают:

Если T является указателем на U или массивом U, его ассоциированные пространства имен и классами являются те, которые связаны с U.

Ответ 2

Набор связанных пространств имен определяется на основе различных типов, видимых из типов аргументов. Примечательно, что для шаблонов классов связанные пространства имен включают связанные пространства имен всех аргументов шаблона. При поиске неквалифицированных функций с использованием зависимого от зависимостей поиска выполняется поиск всех связанных пространств имен.

Список аргументов шаблона foo<int> на самом деле foo<int, std::allocator<int>>, тем самым перетащив пространство имен std в изображение, и уже существует общая перегрузка для swap().