可以定义一个完全通用的 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++) 的实现:
_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 重载 x
,y
是类型 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,其中 x
或 y
是 foo::bar*
,因此将找到 foo::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()
以便它不会被 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++) 的实现:
_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
,也不使用不合格的交换调用。
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-templateswap
overload forfoo::bar
. Leave general swapping tostd::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]p3NullablePointer
要求T*
的左值为Swappable
[nullablepointer.requirements]p1Swappable
本质上需要using std::swap; swap(x, y);
到 select 重载x
,y
是类型T*
[[=72] 的左值=]]p3
在最后一步中,您的类型 foo::bar
产生了歧义,因此违反了 unique_ptr
的要求。 libstdc++ 的实现是一致的,尽管我会说这相当令人惊讶。
措辞当然有点复杂,因为它是通用的。
[unique.ptr]p3
If the type
remove_reference_t<D>::pointer
exists, thenunique_ptr<T, D>::pointer
shall be a synonym forremove_reference_t<D>::pointer
. Otherwiseunique_ptr<T, D>::pointer
shall be a synonym forT*
. The typeunique_ptr<T, D>::pointer
shall satisfy the requirements ofNullablePointer
.
(强调我的)
[nullablepointer.requirements]p1
A
NullablePointer
type is a pointer-like type that supports null values. A typeP
meets the requirements ofNullablePointer
if:
- [...]
- lvalues of type
P
are swappable (17.6.3.2),- [...]
[swappable.requirements]p2
An object
t
is swappable with an objectu
if and only if:
- the expressions
swap(t, u)
andswap(u, t)
are valid when evaluated in the context described below, and- [...]
[swappable.requirements]p3
The context in which
swap(t, u)
andswap(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,其中 x
或 y
是 foo::bar*
,因此将找到 foo::swap
.