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

时间:2016-04-25 20:32:44

标签: c++ c++11 gcc libstdc++ argument-dependent-lookup

以下代码段:

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

3 个答案:

答案 0 :(得分: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类型是一种类似指针的类型,支持null   值。类型P符合NullablePointer if:

的要求      
      
  • [...]
  •   
  • 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作为关联的命名空间。因此swap(x, y)xy的{​​{1}}的ADL会找到foo::bar*

答案 1 :(得分: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 ++)的实现:

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)定位平台,我建议您使用Boost而不是重新发明轮子,以避免像这样的陷阱。

答案 2 :(得分:12)

此技术可用于避免ADL找到foo::swap()

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*明显打破了这个成语。