Можно ли определить полностью общую функцию 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()
так, чтобы он не был затянут поиском Кенига?
3 ответа:
На последнем этапе ваш тип
unique_ptr<T>
требует, чтобыT*
былNullablePointer
[уникальным.ptr]p3NullablePointer
требует, чтобы значенияT*
былиSwappable
[nullablepointer.требования]p1Swappable
существенно требуетusing std::swap; swap(x, y);
выбрать перегрузку дляx
,y
будучи lvalues типаT*
[swappable.требования]p3foo::bar
создает неоднозначность и, следовательно, нарушает требованияunique_ptr
. реализация libstdc++соответствует, хотя я бы сказал, что это скорее удивительный.
Формулировка, конечно, несколько более запутанная, потому что она носит общий характер.[уникальный.ptr]p3
Если существует тип
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.требования] p1
A
NullablePointer
тип - это a тип типа указателя, поддерживающий значение null ценности. ТипP
удовлетворяет требованиямNullablePointer
, Если:
- [...]
- значения типа
P
могут быть заменены (17.6.3.2),- [...]
[заменимый.требования] p2
Объект
t
можно поменять местами с объектомu
тогда и только тогда, когда:
- выражения
swap(t, u)
иswap(u, t)
допустимы при вычислении в контексте, описанном ниже, и- [...]
[заменимый.требования] p3
Контекст, в котором
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
.
Проблема заключается в реализации libstdc++
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 }
Как вы можете видеть, существует неквалифицированный вызов swap. Теперь давайте посмотрим на реализацию libcxx (libc++):
_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
и не используют безусловный вызов swap.
Dip's ответ дает довольно основательную информацию о том, почему
libstdc++
соответствует, но также и о том, почему ваш код будет прерываться всякий раз, когдаswap
требуется вызывать стандартной библиотекой. Процитируем TemplateRex :У вас не должно быть причин определять такой общий шаблон
swap
в очень специфическое пространство имен, содержащее только определенные типы. Просто определите не шаблоннаяswap
перегрузка дляfoo::bar
. Оставьте общий обмен кstd::swap
, и только обеспечивают специфические перегрузки. ИсточникВ качестве примера, это не будет компилироваться:
std::vector<foo::bar> v; std::vector<foo::bar>().swap(v);
Если вы нацелены на платформу со старой стандартной библиотекой / GCC (например, CentOS), я бы рекомендовал использовать Boost вместо изобретения колеса, чтобы избежать подобных ловушек.
Этот метод может быть использован, чтобы избежать
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.Диапазон свободно стоящий
begin()
/end()
определены функции. Я попробовал что-то подобное, прежде чем задать вопрос, но вместо этого сделалusing adl_barrier::swap;
, что не работает.Что касается того, должен ли фрагмент в вопросе работать как есть, я не уверен. Одна сложность, которую я вижу, заключается в том, что
unique_ptr
может иметь пользовательские типыpointer
изDeleter
, которые должны быть заменены обычной идиомойusing std::swap; swap(a, b);
. Тот идиома явно нарушена дляfoo::bar*
в вопросе.