交换右值 [英] Swapping with rvalues

查看:105
本文介绍了交换右值的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我想要在rvalues上可用的swap,并且不想为rvalue/lvalue引用的所有组合编写4个版本(rvalue/rvalue版本有点没有意义,但不会造成伤害).我想到了这个:

template <typename A, typename B>
struct is_same_no_ref
    : std::is_same<
        typename std::remove_reference<A>::type,
        typename std::remove_reference<B>::type
    >
{};

template <typename A, typename B,
    typename = typename std::enable_if<is_same_no_ref<A, B>::value>::type
>
inline void my_swap(A&& a, B&& b) {
    typename std::remove_reference<A>::type t = std::move(a);
    a = std::move(b);
    b = std::move(t);
}

似乎可以正常工作.这个可以吗?还是我错过了一些重要的东西而会让我以后受苦?

解决方案

虽然我发现您的实现中没有固有的概念缺陷,但我会提出三个建议.

(注意:在这里,我将交换右值的概念称为swap_rvalues)


从模板推论中排除const类型

  • 如何?

假设

template <typename A, typename B>
struct is_same_no_ref
    : std::is_same<
        typename std::remove_reference<A>::type,
        typename std::remove_reference<B>::type
    >
{};

启用条件std::enable_if<std::is_same_no_ref<A, B>::value>更改为以下内容.

std::enable_if<
    std::is_same_no_ref<A, B>::value &&
    !std::is_const<typename std::remove_reference<A>::type>::value &&
    !std::is_const<typename std::remove_reference<B>::type>::value
>

  • 为什么?

不从模板推论中排除const类型,而是将const变量传递给swap_rvalues,如下所示,

int const a = 0, b = 0;
swap_rvalues(a, b);

导致编译器标记有关内部实现细节的错误,这不是很用户友好.


启用条件移至返回类型声明

  • 如何?

代替

template<typename A, typename B, typename = typename std::enable_if<...>::type>
inline void swap_rvalues(A&& a, B&& b);

像下面这样声明

template<typename A, typename B>
inline typename std::enable_if<...>::type swap_rvalues(A&& a, B&& b);

  • 为什么?

即使高度不太可能,也可以显式定义swap_rvalues的第三个模板参数,从而有效地覆盖启用条件.这可能允许代码编译不该编译的代码,并且可能导致麻烦.使用 enable条件的返回类型声明完全可以避免这种情况.

请考虑以下示例.

template <
        typename A, 
        typename B, 
        typename = typename std::enable_if<
            is_same_no_ref<A, B>::value &&
            !std::is_const<typename std::remove_reference<A>::type>::value &&
            !std::is_const<typename std::remove_reference<B>::type>::value
        >::type>
inline void
swap(A&& a, B&& b) {
    typename std::remove_reference<A>::type t = std::move(a);
    a = std::move(b);
    b = std::move(t);
}

struct B;
struct A{A(){} A(B const&){}};
struct B{B(){} B(A const&){}};
swap<A, B, void>(A(), B());

即使显然不应该编译,它也可以编译! AB甚至都没有关联,只是在引用彼此的情况下它们才是可构造的.


重复使用代码[aka KISS ]

  • 如何?

由于右值已经已命名,只需转发调用 std::swap ,而不是提供swap_rvalues的全新实现.

  • 为什么?

为什么重新发明轮子†?一旦为右值命名了 std::swap已经提供了预期的行为,那么为什么不重用它呢?


结论

swap_rvalues‡的最终实现如下所示.

template <typename A, typename B>
inline typename std::enable_if<
    is_same_no_ref<A, B>::value &&
    !std::is_const<typename std::remove_reference<A>::type>::value &&
    !std::is_const<typename std::remove_reference<B>::type>::value
>::type
swap_rvalues(A&& a, B&& b) {
    std::swap(a, b);
}


脚注

†​​重新发明轮子,是要复制以前已经由他人创建或优化的基本方法."

swap_rvalues实际上在实际情况下最好称为swap.

Suppose I want swap that works on rvalues, and don't want to write 4 versions for all combinations of rvalue/lvalue references (rvalue/rvalue version is kinda pointless but it doesn't hurt). I came up with this:

template <typename A, typename B>
struct is_same_no_ref
    : std::is_same<
        typename std::remove_reference<A>::type,
        typename std::remove_reference<B>::type
    >
{};

template <typename A, typename B,
    typename = typename std::enable_if<is_same_no_ref<A, B>::value>::type
>
inline void my_swap(A&& a, B&& b) {
    typename std::remove_reference<A>::type t = std::move(a);
    a = std::move(b);
    b = std::move(t);
}

which seems to work as expected. Is this okay? Or am I missing something important that will make me suffer later?

解决方案

While I see no inherent concept flaw in your implementation, I would make three suggestions.

(note: I'll refer here to an implementation of the concept of swaping rvalues as swap_rvalues)


Exclude const types from the template deduction

  • How?

Assuming

template <typename A, typename B>
struct is_same_no_ref
    : std::is_same<
        typename std::remove_reference<A>::type,
        typename std::remove_reference<B>::type
    >
{};

change the enable condition from std::enable_if<std::is_same_no_ref<A, B>::value> into the following.

std::enable_if<
    std::is_same_no_ref<A, B>::value &&
    !std::is_const<typename std::remove_reference<A>::type>::value &&
    !std::is_const<typename std::remove_reference<B>::type>::value
>

  • Why?

Without excluding const types from the template deduction, passing const variables to swap_rvalues, as in the following,

int const a = 0, b = 0;
swap_rvalues(a, b);

induces the compiler to flag errors regarding internal implementation details, what is not very user-friendly.


Move the enable condition to the return type declaration

  • How?

Instead of

template<typename A, typename B, typename = typename std::enable_if<...>::type>
inline void swap_rvalues(A&& a, B&& b);

declare it like the following

template<typename A, typename B>
inline typename std::enable_if<...>::type swap_rvalues(A&& a, B&& b);

  • Why?

Even though highly improbable, the explicit definition of the third template parameter of swap_rvalues is possible, effectively overriding the enable condition. This may allow code to compile which shouldn't and nastiness could follow. This is completely avoided using the return type declaration for the enable condition.

Consider the following example.

template <
        typename A, 
        typename B, 
        typename = typename std::enable_if<
            is_same_no_ref<A, B>::value &&
            !std::is_const<typename std::remove_reference<A>::type>::value &&
            !std::is_const<typename std::remove_reference<B>::type>::value
        >::type>
inline void
swap(A&& a, B&& b) {
    typename std::remove_reference<A>::type t = std::move(a);
    a = std::move(b);
    b = std::move(t);
}

struct B;
struct A{A(){} A(B const&){}};
struct B{B(){} B(A const&){}};
swap<A, B, void>(A(), B());

It compiles, even though it clearly shouldn't! A and B are not even related, they just happen to be constructable given a reference of the other.


Reuse code [aka KISS]

  • How?

Since the rvalues are already given a name, simply forward call std::swap, instead of providing a brand new implementation of swap_rvalues.

  • Why?

Why reinventing the wheel†? std::swap already provides the intended behavior once rvalues are given a name, so why not reusing it?


Conclusion

The final implementation of swap_rvalues‡ would look like follows.

template <typename A, typename B>
inline typename std::enable_if<
    is_same_no_ref<A, B>::value &&
    !std::is_const<typename std::remove_reference<A>::type>::value &&
    !std::is_const<typename std::remove_reference<B>::type>::value
>::type
swap_rvalues(A&& a, B&& b) {
    std::swap(a, b);
}


Footnotes

"To reinvent the wheel is to duplicate a basic method that has already previously been created or optimized by others."

swap_rvalues in fact would better be called swap in a real scenario.

这篇关于交换右值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆