C++11 是否会更改显式调用 std::swap 的行为以确保找到位于 ADL 的交换区(如 boost::swap)?

发布于 2025-01-03 06:58:54 字数 2131 浏览 9 评论 0原文

背景

考虑以下代码:

#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);
}

在 C++03 中,do_swap 的实现将被视为“损坏”:

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::swap(lhs, rhs);
}

通过显式指定 std::,它会禁止 ns ::swap 通过依赖于参数的查找来找到。 (然后它无法编译,因为 std::swap 尝试复制 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
}

现在 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
}

问题

std::swap 是否具有 boost::swap< 的行为/code> 在 C++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 的行为,但我不明白为什么没有进行更改。 :( 而且我并不孤单

Background

Consider the following code:

#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);
}

In C++03, this implementation of do_swap would be considered "broken":

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::swap(lhs, rhs);
}

By explicitly specifying std::, it prohibits ns::swap from being found via argument-dependent lookup. (It then fails to compile because std::swap tries to copy a foo, which is not allowed.) Instead, we do this:

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
}

Now ns::swap is found and std::swap, being less specialized, is not used. It's uglier, but it works and is understandable in hind-sight. boost::swap wraps this up nicely for us (and provides array overloads):

#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
}

Question

Does std::swap take on the behavior of boost::swap in C++11? If not, why?

To me it seems obvious that it ought to. Any code broken by the change was probably quite flimsy in the first place (algorithms and containers, like std::sort and std::vector, were underspecified; implementations were allowed to call ADL swap's or not indeterminately), so the change would be for the better. Additionally, std::swap is now defined for arrays, so change at all certainly isn't out of the question.

However, while §17.6.3.2 specifies that all calls to swap within the standard library must be done without std:: qualification (fixing the problem with algorithms and containers noted above), it fails to touch on std::swap itself. It even gives examples of swapping values that include using std::swap;. Likewise §20.2.2 (where std::swap is specified) doesn't say a word on ADL.

Lastly, GCC does not enable ADL in their std::swap implementation (nor does MSVC, but that's not saying much). So I must be wrong that std::swap takes on the behavior of boost::swap, but I don't understand why the change wasn't made. :( And I'm not alone!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

欢烬 2025-01-10 06:58:54

如果您提出概念验证实施方案,我将不得不投票反对。我担心它会破坏下面的代码,我很确定在过去的十几年里我在野外至少见过一两次。

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);
    }

}

无论您认为上面的代码是好是坏,它都按照作者在 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
}

I would have had to vote against your proof-of-concept implementation had it been proposed. I fear it would break the following code, which I'm pretty sure I've seen in the wild at least once or twice over the past dozen years.

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);
    }

}

Whether you think the above is good code or bad, it works as the author intends in C++98/03 and so the bar for silently breaking it is pretty high. Telling users that in C++11 they would no longer have to write using std::swap; isn't a sufficiently high benefit to outweigh the disadvantage of silently turning the above code into infinite recursion.

Another way to get out of writing using std::swap; is to use std::iter_swap instead:

template <typename T>
void do_swap(T& lhs, T& rhs)
{
    std::iter_swap(&lhs, &rhs); // internally does what do_swap did above
}
韵柒 2025-01-10 06:58:54

在 C++20 中,这最终得到标准化:

std::swap(a, b);

这使用 ADL 调用正确的重载,并提出在 SFINAE 中使用的正确要求。魔法在 [namespace.std]/7 中指定:

除了命名空间 std 或命名空间内的命名空间中
std,程序可以为任何库函数提供重载
指定为定制点的模板,前提是 (a)
重载的声明至少依赖于一种用户定义的类型,并且
(b) 重载满足标准库的要求
自定义点。174 [ 注意: 这允许
(合格或不合格)对要调用的自定义点的调用
给定参数最合适的重载。
结束
注意
]

174) 必须准备任何库自定义点
足以与任何用户定义的满足满足要求的重载一起工作
本文件的最低要求。因此,一个实现可以
根据假设规则([intro.execution]),选择提供任何
实例化函数对象形式的定制点
([function.objects]) 即使定制点的
规范采用函数​​模板的形式。模板
每个这样的函数对象的参数和函数参数
并且对象的 operator() 的返回类型必须与
对应定制点的规格。

(强调我的)

并且 swap 被指定为 [utility.swap]

模板
  constexpr void swap(T& a, T& b) noexcept(见下文);

备注: 此函数是指定的自定义点([namespace.std]),不应参与重载决策
除非 is_move_constructible_vtrue 并且
is_move_assignable_vtrue。里面的表情
noexcept 相当于:

is_no throw_move_constructible_v; && is_nothrow_move_assignable_v

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

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

(强调我的)

In C++20, this is finally standardized:

std::swap(a, b);

This uses ADL to call the correct overload and imposes the correct requirements to use in SFINAE. The magic is specified in [namespace.std]/7:

Other than in namespace std or in a namespace within namespace
std, a program may provide an overload for any library function
template designated as a customization point, provided that (a) the
overload's declaration depends on at least one user-defined type and
(b) the overload meets the standard library requirements for the
customization point.174 [ Note: This permits a
(qualified or unqualified) call to the customization point to invoke
the most appropriate overload for the given arguments.
— end
note
 ]

174) Any library customization point must be prepared
to work adequately with any user-defined overload that meets the
minimum requirements of this document. Therefore an implementation may
elect, under the as-if rule ([intro.execution]), to provide any
customization point in the form of an instantiated function object
([function.objects]) even though the customization point's
specification is in the form of a function template. The template
parameters of each such function object and the function parameters
and return type of the object's operator() must match those of the
corresponding customization point's specification.

(emphasis mine)

And swap is designated as a customization point in [utility.swap]:

template<class T>
  constexpr void swap(T& a, T& b) noexcept(see below);

Remarks: This function is a designated customization point ([namespace.std]) and shall not participate in overload resolution
unless is_­move_­constructible_­v<T> is true and
is_­move_­assignable_­v<T> is true. The expression inside
noexcept is equivalent to:

is_nothrow_move_constructible_v<T> && is_nothrow_move_assignable_v<T>

Requires: Type T shall be Cpp17MoveConstructible (Table 26) and Cpp17MoveAssignable (Table 28).

Effects: Exchanges values stored in two locations.

(emphasis mine)

听风吹 2025-01-10 06:58:54

这是一个概念验证的实现:

#include <utility>

// exposition implementation
namespace std_
{
    namespace detail
    {
        // actual fallback implementation
        template <typename T>
        void swap(T& lhs, T& rhs)
        {
            T temp = std::move(lhs);
            lhs = std::move(rhs);
            rhs = std::move(temp);
        }
    }

    template <typename T>
    void swap(T& lhs, T& rhs)
    {
        using detail::swap; // shadows std_::swap, stops recursion
        swap(lhs, rhs); // unqualified call, allows ADL
    }
}

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);
    }
}


int main()
{
    int i = 0, j = 0;
    std_::swap(i, j);

    ns::foo a, b;
    std_::swap(a, b);
}

Here's a proof-of-concept implementation:

#include <utility>

// exposition implementation
namespace std_
{
    namespace detail
    {
        // actual fallback implementation
        template <typename T>
        void swap(T& lhs, T& rhs)
        {
            T temp = std::move(lhs);
            lhs = std::move(rhs);
            rhs = std::move(temp);
        }
    }

    template <typename T>
    void swap(T& lhs, T& rhs)
    {
        using detail::swap; // shadows std_::swap, stops recursion
        swap(lhs, rhs); // unqualified call, allows ADL
    }
}

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);
    }
}


int main()
{
    int i = 0, j = 0;
    std_::swap(i, j);

    ns::foo a, b;
    std_::swap(a, b);
}
影子是时光的心 2025-01-10 06:58:54

好吧,boost::swap() 分派到 std::swap()。要让 std::swap() 执行类似于 boost::swap() 的操作,需要将其委托给其他地方。这在别处是什么?该标准没有强制要求提供实际实现的 swap() 的另一个版本。这是可以做到的,但标准没有强制这样做。

为什么它不这样做?我没有看到任何建议实施此实施的提案。如果有人希望这样做,我相信它会被提议。

Well, boost::swap() dispatches to std::swap(). To have std::swap() do something similar to boost::swap() it would need to delegate somewhere else. What is this somewhere else? The standard doesn't mandate another version of swap() which provides the actual implementation. This can be done but the standard doesn't mandate it.

Why it doesn't do it? I didn't see any proposal proposing this implementation. If someone had wanted this to do done I'm sure it would have been proposed.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文