C++ 11是否会改变显式调用std :: swap的行为以确保找到ADL定位的swap,如boost :: swap?

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)


L. *_* F. 6

在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]中指定为定制点:

template<class T>
  constexpr void swap(T& a, T& b) noexcept(see below);
Run Code Online (Sandbox Code Playgroud)

备注: 该函数是指定的自定义点([namespace.std]),除非is_­move_­constructible_­v<T>is trueis_­move_­assignable_­v<T>is,否则不得参与重载解析true。内部的表达式 noexcept等效于:

is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>
Run Code Online (Sandbox Code Playgroud)

要求:类型T应为Cpp17MoveConstructible(表26)和Cpp17MoveAssignable(表28)。

效果:交换存储在两个位置的值。

(强调我的)

  • 那么这如何与霍华德答案中的示例代码交互呢? (2认同)
  • @aschepler *默默地将上面的代码变成无限递归* :( (2认同)