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

Tav*_*nes 39 c++ gcc libstdc++ argument-dependent-lookup c++11

以下片段:

#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();
}
Run Code Online (Sandbox Code Playgroud)

不为我编译:

$ 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)
Run Code Online (Sandbox Code Playgroud)

这是我的错,因为声明一个swap()如此通用的函数会与它发生冲突std::swap吗?

如果是这样,有没有办法定义,foo::swap()以便它不被Koenig查找拖入?

dyp*_*dyp 25

  • unique_ptr<T>需要T*NullablePointer[unique.ptr] p3
  • NullablePointer需要的左值T*Swappable[nullablepointer.requirements] P1
  • Swappable本质上需要using std::swap; swap(x, y);选择一个重载x,yT*[swappable.requirements] p3 类型的左值

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


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

[unique.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.requirements] P1

NullablePointer型是支持空值的指针般类型.类型P符合以下要求NullablePointer:

  • [...]
  • 类型P的左值是可交换的(17.6.3.2),
  • [...]

[swappable.requirements] P2

当且仅当以下情况时t,对象u才可与对象交换:

  • 表达式swap(t, u)swap(u, t)在下面描述的上下文中计算时是有效的,并
  • [...]

[swappable.requirements] P3

其中swap(t, u)swap(u, t)被评估的上下文应确保通过候选集上的重载决策选择名为"swap"的二进制非成员函数,该候选集包括:

  • swap<utility>和中定义的两个函数模板
  • 由参数依赖查找生成的查找集.

请注意,对于指针类型T*,出于ADL的目的,关联的命名空间和类是从类型派生的T.因此,foo::bar*具有foo关联的命名空间.ADL的swap(x, y)情形之一x或者yfoo::bar*将因此找到foo::swap.


小智 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       }
Run Code Online (Sandbox Code Playgroud)

如您所见,有一个不合格的交换调用.现在让我们看看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_);}
Run Code Online (Sandbox Code Playgroud)

他们不会swap在内部打电话,reset也不会使用不合格的掉期电话.


Dyp的答案提供了一个非常可靠的细分,说明了为什么libstdc++符合要求,以及为什么只要swap标准库需要调用你的代码就会中断.引用TemplateRex:

您没有理由swap在仅包含特定类型的特定命名空间中定义此类通用模板.只需swap为其定义非模板重载foo::bar.保持一般交换std::swap,并仅提供特定的重载.资源

作为一个例子,这将无法编译:

std::vector<foo::bar> v;
std::vector<foo::bar>().swap(v);
Run Code Online (Sandbox Code Playgroud)

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


Tav*_*nes 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;
}
Run Code Online (Sandbox Code Playgroud)

这就是Boost.Range的独立begin()/ end()功能的定义方式.在问这个问题之前我尝试了类似的东西,但确实做了,但是没有using adl_barrier::swap;用.

至于问题中的片段是否应该按原样运作,我不确定.我可以看到的一个复杂因素是unique_ptr可以使用自定义pointer类型Deleter,它应该与通常的using std::swap; swap(a, b);习惯用法交换.foo::bar*在这个问题中,这个习语明显被打破了.