Можно ли определить полностью общую функцию 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 39

3 ответа:

  • unique_ptr<T> требует, чтобы T* был NullablePointer [уникальным.ptr]p3
  • NullablePointer требует, чтобы значения T* были Swappable [nullablepointer.требования]p1
  • Swappable существенно требует using std::swap; swap(x, y); выбрать перегрузку для x, y будучи lvalues типа T* [swappable.требования]p3
На последнем этапе ваш тип foo::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++):

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 и не используют безусловный вызов 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* в вопросе.