假设我想要适用于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的第三个模板参数,从而有效地覆盖启用条件。这可以允许编译不应该的代码,并且可能导致麻烦。将返回类型声明用于启用条件可以完全避免这种情况。
    考虑以下示例。
    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的全新实现。
  • 为什么?

  • 为什么是reinventing the wheel†?一旦给右值命名,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

    关于c++ - 与右值交换,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/18942438/

    10-12 15:05