2016-04-25 2 views
39

Следующий фрагмент кода:Можно ли определить полностью общую функцию 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?

+0

Не компилируется на GCC или Clang, но компилируется на MSVC 2015. Возможно, другая недокументированная функция. – wally

+0

Черт, это хорошие отношения в течение менее первого часа. +16 upvotes, просмотрено 77 раз и 4 фаворита. –

+1

У вас не должно быть оснований для определения такого общего шаблона 'swap' в очень специфическом пространстве имен, содержащем только определенные типы. Просто определите перегрузку без шаблона 'swap' для' foo :: bar'. Оставьте общую замену на 'std :: swap' и укажите только определенные перегрузки. – TemplateRex

ответ

25
  • unique_ptr<T> требует T* быть NullablePointer [unique.ptr] р3
  • NullablePointer требует lvalues ​​из T* быть Swappable [nullablepointer.requirements] p1
  • Swappable по существу требует, чтобы выбрать using std::swap; swap(x, y); перегрузку для x, y, являющиеся значениями типа T* [заменяемые. Требования] p3

На последнем этапе ваш тип foo::bar создает двусмысленность и поэтому нарушает требования unique_ptr. реализация libstdC++ соответствует, хотя я бы сказал, что это довольно удивительно.


Формулировка, конечно, немного более запутанная, поскольку она является общей.

[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] p1

NullablePointer типа является указателем-подобного типа, который поддерживает нулевые значения . Тип P отвечает требованиям NullablePointer если:

  • [...]
  • lvalues ​​типа P заменяемы (17.6.3.2),
  • [...]

[заменяемый.требования] p2

t Объект является замена с объектом u, если и только если:

  • выражения swap(t, u) и swap(u, t) действительны при оценке в контексте описанной ниже, и
  • [.. .]

[swappable.requirements] р3

Контекст, в котором swap(t, u) и swap(u, t) оцениваются должны гарантировать, что бинарная функция не-член с именем «своп» выбирается с помощью разрешения перегрузки по набору кандидатов, который включает в себя:

  • двух swap шаблонов функций определенные в <utility> и
  • набор поиска, созданный зависящим от аргумента поиска.

Обратите внимание, что для типа указателя T*, для целей ADL, ассоциированные пространства имен и классы являются производными от типа T. Следовательно, foo::bar* имеет foo как ассоциированное пространство имен. ADL для swap(x, y), где x или y является foo::bar*, поэтому найдет foo::swap.

+0

Вы можете найти более простой раздел [здесь] (http://en.cppreference.com/w/cpp/concept/Swappable) для тех, кто не говорит адвоката. В любом случае, в то время как libstdC++, безусловно, следит за правилами здесь, похоже, нет ничего, что требовало бы использования ADL swap внутри сброса, учитывая, что обмен файлами libcxx по функциональности эквивалентен. Я сделаю это до качества реализации. – user6253369

+0

@ user6253369 это QoI на части OP здесь. Просто не предоставляйте общие шаблоны, дублирующие точки настройки шаблонов STL, и особенно не предоставляйте свои собственные классы таким шаблонам STL, когда вы это делаете. – TemplateRex

+1

@ user6253369 Для причудливых указателей, которые фактически определяют пользовательские свопы, использование этих параметров может быть более эффективным. –

12

Этот метод может быть использован, чтобы избежать 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, которые должны быть заменены обычным using std::swap; swap(a, b); идиомой. Эта идиома явно сломана для foo::bar* в вопросе.

+1

Если это «полностью общий», то почему бы вам просто не импортировать 'std :: swap' в' foo' с помощью объявления-объявления? – dyp

+1

@dyp 'foo :: swap()' было написано, потому что одна из наших платформ имеет стандартную библиотеку с недостатком 'std :: swap()' (копирует, а не перемещает). Вероятно, я буду использовать 'std :: swap'', кроме этой платформы. –

+0

Кстати, я думаю, что лучше использовать ADL-защиту вашего типа (например, 'foo :: bar') вместо ваших функций (например,' foo :: swap'), поскольку это те, которые вызывают горе, и количество функций в 'namespace foo' является открытым (кто-то может добавить' foo :: begin' и 'foo :: end' someday, а затем у вас появятся новые проблемы, защищенные ADL' foo :: bar' против этого). – TemplateRex

14

Проблема заключается в реализации 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  } 

Как вы можете видеть, есть неквалифицированный своп вызова. Теперь давайте посмотрим libcxx (LibC++) реализации 's:

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's answer обеспечивает довольно твердый разбивку о том, почему libstdc++ в соответствии, но и почему ваш код будет перерыв, когда swap требуется назвать стандартной библиотекой. Цитирую TemplateRex:

Вы не должны иметь никаких оснований для определения такого общего swap шаблон в очень определенное пространство имен, содержащее только определенные типы. Просто определите не-шаблон swap перегрузка для foo::bar. Оставьте общую замену на std::swap и укажите только конкретные перегрузки.source

В качестве примера, это не будет компилироваться:

std::vector<foo::bar> v; 
std::vector<foo::bar>().swap(v); 

Если вы ориентируетесь на платформу со старой стандартной библиотеки/GCC (например, CentOS), я бы рекомендовал использовать подталкивание вместо изобретая колесо, чтобы избежать подобных ошибок.

+0

«Я бы рекомендовал использовать Boost вместо того, чтобы изобретать колесо» Согласен! Мы делаем это для многих вещей в 'foo'. К сожалению, 'boost :: swap()' не выполняет переадресации, а просто делегирует 'std :: swap'. –

+0

@TavianBarnes Это может быть правдой, но я считаю, что эквивалент Boost unique_ptr использует семантику перемещения для свопа, например. – user6253369

+0

Правильно, но я часто хочу сделать 'swap (a, b);' где 'a' и' b' имеют тип, не подлежащий копированию. Это боль, чтобы объявить перегруз для * каждого * только для перемещения типа, который я определяю, поэтому мне нужна реализация 'swap()', которая перемещается. –

Смежные вопросы