GMa*_*ckG 29 c++ swap rationale language-lawyer c++11
考虑以下代码:
#include <utility>
namespace ns
{
struct foo
{
foo() : i(0) {}
int i;
private:
foo(const foo&); // not defined,
foo& operator=(const foo&); // non-copyable
};
void swap(foo& lhs, foo& rhs)
{
std::swap(lhs.i, rhs.i);
}
}
template <typename T>
void do_swap(T& lhs, T& rhs); // implementation to be determined
int main()
{
ns::foo a, b;
do_swap(a, b);
}
Run Code Online (Sandbox Code Playgroud)
在C++ 03中,这种实现do_swap将被视为"已损坏":
template <typename T>
void do_swap(T& lhs, T& rhs)
{
std::swap(lhs, rhs);
}
Run Code Online (Sandbox Code Playgroud)
通过明确指定std::,它禁止ns::swap通过参数依赖查找找到它.(然后它无法编译,因为std::swap尝试复制a foo,这是不允许的.)相反,我们这样做:
template <typename T>
void do_swap(T& lhs, T& rhs)
{
using std::swap; // allow std::swap as a backup if ADL fails to find a swap
swap(lhs, rhs); // unqualified call to swap, allow ADL to operate
}
Run Code Online (Sandbox Code Playgroud)
现在ns::swap已经找到std::swap,并且不那么专业,没有使用.这很丑陋,但它的作用在后视中是可以理解的.boost::swap为我们很好地包装它(并提供数组重载):
#include <boost/swap.hpp>
template <typename T>
void do_swap(T& lhs, T& rhs)
{
boost::swap(lhs, rhs); // internally does what do_swap did above
}
Run Code Online (Sandbox Code Playgroud)
我的问题是:确实std::swap采用了boost::swapC++ 11中的行为?如果没有,为什么?
对我而言,它应该是显而易见的.任何被更改破坏的代码在第一时间都可能非常脆弱(算法和容器,例如,std::sort并且std::vector,未指定;实现被允许调用ADL交换或不确定),因此更改将更好.另外,std::swap现在已经为数组定义了,所以改变当然不是不可能的.
但是,虽然§17.6.3.2规定swap必须在没有std::限定条件的情况下完成对标准库内的所有调用(修复上面提到的算法和容器的问题),但它无法触及std::swap自身.它甚至提供了包含交换值的示例using std::swap;.同样地,§20.2.2(在std::swap指定的地方)没有对ADL说一句话.
最后,GCC没有在std::swap实现中启用ADL (MSVC也没有,但这并没有说太多).所以我一定是错的,std::swap承担了行为boost::swap,但我不明白为什么没有做出改变.:( 而我并不孤单!
How*_*ant 27
如果提出建议,我将不得不投票反对你的概念验证实施.我担心它会破坏下面的代码,我非常肯定我在过去的十几年里至少在野外看过一两次.
namespace oops
{
struct foo
{
foo() : i(0) {}
int i;
void swap(foo& x) {std::swap(*this, x);}
};
void swap(foo& lhs, foo& rhs)
{
lhs.swap(rhs);
}
}
Run Code Online (Sandbox Code Playgroud)
无论你认为上面是好的代码还是坏的,它都可以像C++ 98/03中的作者那样工作,因此默默地打破它的标准非常高.告诉那些在C++ 11中他们不再需要编写using std::swap;的用户并不是一个足够高的好处,而是超过了将上述代码无声地转换为无限递归的缺点.
另一种退出写作的方法using std::swap;是使用std::iter_swap:
template <typename T>
void do_swap(T& lhs, T& rhs)
{
std::iter_swap(&lhs, &rhs); // internally does what do_swap did above
}
Run Code Online (Sandbox Code Playgroud)
在C ++ 20中,这终于标准化了:
std::swap(a, b);
Run Code Online (Sandbox Code Playgroud)
这使用ADL调用正确的重载,并提出了在SFINAE中使用的正确要求。魔法是在[namespace.std] / 7中指定的:
除了在名称空间中
std或在名称空间中的名称空间中std,程序可以为指定为定制点的任何库函数模板提供重载,条件是(a)重载的声明取决于至少一个用户定义的类型,并且(b)重载满足定制点的标准库要求。174 [?注意: 这允许对定制点进行(合格或不合格)调用,以为给定参数调用最合适的重载。-?尾注?]174)必须准备任何库定制点,以使其足以满足本文档的最低要求的任何用户定义的重载。因此,即使定制点的规范采用以下形式,实现也可以在假设规则([intro.execution])下选择以实例化函数对象([function.objects])的形式提供任何定制点。功能模板。每个此类功能对象的模板参数以及该对象的功能参数和返回类型
operator()必须与相应的定制点的规范相匹配。
(强调我的)
并swap在[utility.swap]中指定为定制点:
Run Code Online (Sandbox Code Playgroud)template<class T> constexpr void swap(T& a, T& b) noexcept(see below);备注: 该函数是指定的自定义点([namespace.std]),除非
is_move_constructible_v<T>istrue和is_move_assignable_v<T>is,否则不得参与重载解析true。内部的表达式noexcept等效于:Run Code Online (Sandbox Code Playgroud)is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>要求:类型
T应为Cpp17MoveConstructible(表26)和Cpp17MoveAssignable(表28)。效果:交换存储在两个位置的值。
(强调我的)