以下代码段:
#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 :(得分:25)
unique_ptr<T>
要求T*
为NullablePointer
[unique.ptr] p3 NullablePointer
要求T*
的左值为Swappable
[nullablepointer.requirements] p1 Swappable
基本上需要using std::swap; swap(x, y);
为x
选择重载,y
为T*
类型的左值[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)
或x
为y
的{{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 ++)的实现:
_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*
明显打破了这个成语。