Можно ли определить полностью общую функцию swap()?
Следующий фрагмент:
#include <memory>
#include <utility>
namespace foo
{
template <typename T>
void swap(T& a, T& b)
{
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
struct bar { };
}
void baz()
{
std::unique_ptr<foo::bar> ptr;
ptr.reset();
}
не компилируется для меня:
$ g++ -std=c++11 -c foo.cpp
In file included from /usr/include/c++/5.3.0/memory:81:0,
from foo.cpp:1:
/usr/include/c++/5.3.0/bits/unique_ptr.h: In instantiation of ‘void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = foo::bar; _Dp = std::default_delete<foo::bar>; std::unique_ptr<_Tp, _Dp>::pointer = foo::bar*]’:
foo.cpp:20:15: required from here
/usr/include/c++/5.3.0/bits/unique_ptr.h:342:6: error: call of overloaded ‘swap(foo::bar*&, foo::bar*&)’ is ambiguous
swap(std::get<0>(_M_t), __p);
^
In file included from /usr/include/c++/5.3.0/bits/stl_pair.h:59:0,
from /usr/include/c++/5.3.0/bits/stl_algobase.h:64,
from /usr/include/c++/5.3.0/memory:62,
from foo.cpp:1:
/usr/include/c++/5.3.0/bits/move.h:176:5: note: candidate: void std::swap(_Tp&, _Tp&) [with _Tp = foo::bar*]
swap(_Tp& __a, _Tp& __b)
^
foo.cpp:7:10: note: candidate: void foo::swap(T&, T&) [with T = foo::bar*]
void swap(T& a, T& b)
Является ли это моей ошибкой для объявления функции swap()
настолько общей, что она конфликтует с std::swap
?
Если да, существует ли способ определить foo::swap()
так, чтобы он не попал под поиск Koenig?
Ответы
Ответ 1
-
unique_ptr<T>
требует T*
быть NullablePointer
[unique.ptr] p3
-
NullablePointer
требуется lvalues T*
быть Swappable
[nullablepointer.requirements] p1
-
Swappable
по существу требует using std::swap; swap(x, y);
для выбора перегрузки для x
, y
, являющейся lvalues типа T*
[swappable.requirements] p3
На последнем шаге ваш тип foo::bar
создает двусмысленность и, следовательно, нарушает требования unique_ptr
. реализация libstdС++ соответствует, хотя я бы сказал, что это довольно неожиданно.
Формулировка, конечно, немного запутана, потому что она является общей.
[unique.ptr] р3
Если тип remove_reference_t<D>::pointer
существует, то unique_ptr<T, D>::pointer
будет синонимом remove_reference_t<D>::pointer
. В противном случае unique_ptr<T,
D>::pointer
должен быть синонимом T*
. Тип unique_ptr<T,
D>::pointer
должен удовлетворять требованиям NullablePointer
.
(акцент мой)
[nullablepointer.requirements] р1
A NullablePointer
type - это тип, похожий на указатель, который поддерживает null значения. Тип P
соответствует требованиям NullablePointer
, если:
- [...]
- lvalues типа
P
заменяемы (17.6.3.2), - [...]
[swappable.requirements] р2
Объект t
заменяется с объектом u
тогда и только тогда, когда:
- выражения
swap(t, u)
и swap(u, t)
действительны при оценке в контексте, описанном ниже, и - [...]
[swappable.requirements] р3
Контекст, в котором оцениваются swap(t, u)
и swap(u, t)
, должен убедитесь, что функция двоичного нечлена с именем "swap" выбрана через разрешение перегрузки в наборе кандидатов, которое включает в себя:
- два шаблона функций
swap
, определенные в <utility>
и - набор поиска, созданный зависимым от аргумента поиска.
Обратите внимание, что для типа указателя T*
для целей ADL соответствующие пространства имен и классы производятся от типа t
. Следовательно, foo::bar*
имеет foo
как ассоциированное пространство имен. ADL для swap(x, y)
, где либо x
, либо y
является foo::bar*
, поэтому найдет foo::swap
.
Ответ 2
Проблема заключается в реализации libstdС++ unique_ptr
. Это из их ветки 4.9.2:
https://gcc.gnu.org/onlinedocs/gcc-4.9.2/libstdc++/api/a01298_source.html#l00339
338 void
339 reset(pointer __p = pointer()) noexcept
340 {
341 using std::swap;
342 swap(std::get<0>(_M_t), __p);
343 if (__p != pointer())
344 get_deleter()(__p);
345 }
Как вы можете видеть, существует безоговорочный своп-вызов. Теперь рассмотрим реализацию libcxx (libС++):
https://git.io/vKzhF
_LIBCPP_INLINE_VISIBILITY void reset(pointer __p = pointer()) _NOEXCEPT
{
pointer __tmp = __ptr_.first();
__ptr_.first() = __p;
if (__tmp)
__ptr_.second()(__tmp);
}
_LIBCPP_INLINE_VISIBILITY void swap(unique_ptr& __u) _NOEXCEPT
{__ptr_.swap(__u.__ptr_);}
Они не вызывают swap
внутри reset
и не используют неквалифицированный обменный вызов.
Dyp answer дает довольно прочную разбивку о том, почему libstdc++
соответствует, а также почему ваш код будет прерываться всякий раз, когда swap
требуется вызывать по стандарту библиотека. Чтобы процитировать TemplateRex:
У вас не должно быть никаких оснований определять такой общий шаблон swap
в очень специфическое пространство имен, содержащее только определенные типы. Просто определите перегрузка без шаблона swap
для foo::bar
. Оставить общую свопинг до std::swap
и обеспечивают только определенные перегрузки. источник
В качестве примера это не будет компилироваться:
std::vector<foo::bar> v;
std::vector<foo::bar>().swap(v);
Если вы ориентируетесь на платформу со старой стандартной библиотекой /GCC (например, CentOS), я бы рекомендовал использовать Boost вместо того, чтобы изобретать колесо, чтобы избежать подобных ошибок.
Ответ 3
Этот метод можно использовать, чтобы избежать foo::swap()
обнаружения ADL:
namespace foo
{
namespace adl_barrier
{
template <typename T>
void swap(T& a, T& b)
{
T tmp = std::move(a);
a = std::move(b);
b = std::move(tmp);
}
}
using namespace adl_barrier;
}
Так определяются функции Boost.Range отдельно стоящие функции begin()
/end()
. Я попробовал что-то подобное, прежде чем задавать вопрос, но вместо этого сделал using adl_barrier::swap;
, который не работает.
Что касается того, должен ли фрагмент в вопросе работать как есть, я не уверен. Одно из осложнений, которое я вижу, состоит в том, что unique_ptr
может иметь пользовательские типы pointer
из Deleter
, которые должны быть заменены обычным idiom using std::swap; swap(a, b);
. Эта идиома явно нарушена для foo::bar*
в вопросе.