可以定义一个完全通用的 swap() 函数吗?

Is it okay to define a totally general swap() function?

以下代码段:

#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 查找拖入?

此技术可用于避免 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 可以从 Deleter 中拥有自定义的 pointer 类型,这应该与通常的 using std::swap; swap(a, b); 习语交换。问题中 foo::bar* 的成语显然是错误的。

问题是 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++) 的实现:

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_);}

他们不在 reset 内调用 swap,也不使用不合格的交换调用。


provides a pretty solid breakdown on why libstdc++ is conforming but also why your code will break whenever swap is required to be called by the standard library. To quote TemplateRex:

You should have no reason to define such a general swap template in a very specific namespace containing only specific types. Just define a non-template swap overload for foo::bar. Leave general swapping to std::swap, and only provide specific overloads.

例如,这不会编译:

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

如果您的目标平台是旧标准 library/GCC(如 CentOS),我建议您使用 Boost 而不是重新发明轮子以避免这样的陷阱。

  • unique_ptr<T> 要求 T* 成为 NullablePointer [unique.ptr]p3
  • NullablePointer 要求 T* 的左值为 Swappable [nullablepointer.requirements]p1
  • Swappable 本质上需要 using std::swap; swap(x, y); 到 select 重载 xy 是类型 T* [[=72] 的左值=]]p3

在最后一步中,您的类型 foo::bar 产生了歧义,因此违反了 unique_ptr 的要求。 libstdc++ 的实现是一致的,尽管我会说这相当令人惊讶。


措辞当然有点复杂,因为它是通用的。

[unique.ptr]p3

If the type remove_reference_t<D>::pointer exists, then unique_ptr<T, D>::pointer shall be a synonym for remove_reference_t<D>::pointer. Otherwise unique_ptr<T, D>::pointer shall be a synonym for T*. The type unique_ptr<T, D>::pointer shall satisfy the requirements of NullablePointer.

(强调我的)

[nullablepointer.requirements]p1

A NullablePointer type is a pointer-like type that supports null values. A type P meets the requirements of NullablePointer if:

  • [...]
  • lvalues of type P are swappable (17.6.3.2),
  • [...]

[swappable.requirements]p2

An object t is swappable with an object u if and only if:

  • the expressions swap(t, u) and swap(u, t) are valid when evaluated in the context described below, and
  • [...]

[swappable.requirements]p3

The context in which swap(t, u) and swap(u, t) are evaluated shall ensure that a binary non-member function named “swap” is selected via overload resolution on a candidate set that includes:

  • the two swap function templates defined in <utility> and
  • the lookup set produced by argument-dependent lookup.

请注意,对于指针类型 T*,出于 ADL 的目的,关联的命名空间和 类 派生自类型 T。因此,foo::bar* 具有 foo 作为关联的名称空间。 swap(x, y) 的 ADL,其中 xyfoo::bar*,因此将找到 foo::swap.