【问题标题】:How to make template rvalue reference parameter ONLY bind to rvalue reference?如何使模板右值引用参数仅绑定到右值引用?
【发布时间】:2011-12-13 09:33:03
【问题描述】:

我正在编写一个网络库并大量使用移动语义来处理文件描述符的所有权。我的一个班级希望接收其他类型的文件描述符包装器并获得所有权,所以它类似于

struct OwnershipReceiver
{
  template <typename T>
  void receive_ownership(T&& t)
  {
     // taking file descriptor of t, and clear t
  }
};

它必须处理多个不相关的类型,因此 receive_ownership 必须是一个模板,并且为了安全起见,我希望它只绑定到右值引用,以便用户在传递左值时必须显式声明 std::move。

receive_ownership(std::move(some_lvalue));

但问题是:C++ 模板推导允许传入一个左值而不需要额外的努力。实际上,我不小心将一个左值传递给了receive_ownership,并在以后使用该左值(清除),实际上是在开枪打死自己。

那么问题来了:如何让模板只绑定到右值引用?

【问题讨论】:

    标签: c++ rvalue-reference


    【解决方案1】:

    您可以将T 限制为不是左值引用,从而防止左值绑定到它:

    #include <type_traits>
    
    struct OwnershipReceiver
    {
      template <typename T,
                class = typename std::enable_if
                <
                    !std::is_lvalue_reference<T>::value
                >::type
               >
      void receive_ownership(T&& t)
      {
         // taking file descriptor of t, and clear t
      }
    };
    

    T 添加某种限制以使其仅接受文件描述符包装器也是一个好主意。

    【讨论】:

    • 谢谢霍华德,这很好。戴夫,我和你一开始的想法完全一样,然后我发现 std::is_rvalue_reference 不起作用:它不会绑定到“真正的”右值,甚至不会绑定到 std::move()ed 左值。
    • @Ralph:你试过is_rvalue_reference&lt;T&amp;&amp;&gt;::value吗? (注意&amp;&amp;
    • @fredoverflow,那么,应该使用!std::is_lvalue_reference&lt;T&gt;::valuestd::is_rvalue_reference&lt;T&amp;&amp;&gt;::value 什么? (第二个看起来更优雅,因为它告诉你 wat t 真的是。
    • @alfC 现在stackoverflow.com/questions/53758796/…给出了很好的解释
    • @solstice333:搜索“万能参考”和“完美转发”。
    【解决方案2】:

    一个简单的方法是提供一个接受左值引用的已删除成员

    template<typename T> void receive_ownership(T&) = delete;
    

    这总是更适合左值参数。


    如果您有一个带有多个参数的函数,所有这些参数都需要是右值,我们将需要几个已删除的函数。在这种情况下,我们可能更喜欢使用 SFINAE 对任何左值参数隐藏函数。

    使用 C++17 和 Concepts TS 可以做到这一点:

    #include <type_traits>
    
    template<typename T>
    void receive_ownership(T&& t)
        requires !std::is_lvalue_reference<T>::value
    {
         // taking file descriptor of t, and clear t
    }
    

    #include <type_traits>
    
    void receive_ownership(auto&& t)
        requires std::is_rvalue_reference<decltype(t)>::value
    {
         // taking file descriptor of t, and clear t
    }
    

    再进一步,您可以定义自己的新概念,如果您想重复使用它,或者只是为了更加清晰,这可能会很有用:

    #include <type_traits>
    
    template<typename T>
    concept bool rvalue = std::is_rvalue_reference<T&&>::value;
    
    
    void receive_ownership(rvalue&& t)
    {
         // taking file descriptor of t, and clear t
    }
    

    注意:对于 GCC 6.1,您需要将 -fconcepts 传递给编译器,因为它是 C++17 的扩展,而不是它的核心部分。

    为了完整起见,这是我的简单测试:

    #include <utility>
    int main()
    {
        int a = 0;
        receive_ownership(a);       // error
        receive_ownership(std::move(a)); // okay
    
        const int b = 0;
        receive_ownership(b);       // error
        receive_ownership(std::move(b)); // allowed - but unwise
    }
    

    【讨论】:

    • 我不能template &lt;typename T&gt; void receive_ownership(T&amp; t) = delete吗?
    • 是的,这很有效,而且更简单——我已经进行了相应的编辑。谢谢你的提示。
    • 但是不应该是const T&amp;还是两者兼而有之?
    • 我不这么认为,因为T 将绑定到const 以及非const 类型。我确实尝试了这个例子。
    • T 会绑定,但它是T&amp;。它不会绑定到const Tconst T&amp; 对象
    【解决方案3】:

    我学到了一些似乎经常让人们感到困惑的东西:使用 SFINAE 可以,但我不能使用:

    std::is_rvalue_reference<T>::value
    

    它按我的意愿工作的唯一方法是

    !std::is_lvalue_reference<T>::value
    

    原因是:我需要我的函数接收rvalue,而不是rvaluereference。使用std::is_rvalue_reference&lt;T&gt;::value 有条件启用的函数不会接收右值,而是接收右值引用。

    【讨论】:

      【解决方案4】:

      对于左值引用,推论 T 为左值引用,而对于右值引用,推论 T 为非引用。

      所以如果函数绑定到一个右值引用,编译器最后看到的某个类型 T 是:

      std::is_rvalue_reference&lt;T&gt;::value

      而不是

      std::is_rvalue_reference&lt;T&amp;&amp;&gt;::value

      【讨论】:

        【解决方案5】:

        不幸的是,如果您实际上是在尝试创建区分const T&amp;T&amp;&amp; 的重载,那么尝试is_rvalue_reference&lt;TF&gt;(其中TF 是完美转发类型)似乎效果不佳(例如,两者都使用enable_if,一个使用is_rvalue_reference_v&lt;TF&gt;,另一个使用!is_rvalue_reference_V&lt;TF&gt;)。

        一个解决方案(虽然很老套)是衰减转发的T,然后将重载放在一个知道这些类型的容器中。生成this example

        嘿,我错了,只是忘了看 Toby 的答案 (is_rvalue_reference&lt;TF&amp;&amp;&gt;) —— 虽然你可以做 std::forward&lt;TF&gt;(...) 会让人困惑,但我想这就是为什么 decltype(arg) 也有效。

        任何人,这是我用于调试的内容:(1) 使用 struct 重载,(2) 对 is_rvalue_reference 使用错误检查,以及 (3) 正确检查:

        /*
        Output:
        
        const T& (struct)
        const T& (sfinae)
        const T& (sfinae bad)
        ---
        const T& (struct)
        const T& (sfinae)
        const T& (sfinae bad)
        ---
        T&& (struct)
        T&& (sfinae)
        const T& (sfinae bad)
        ---
        T&& (struct)
        T&& (sfinae)
        const T& (sfinae bad)
        ---
        */
        
        #include <iostream>
        #include <type_traits>
        
        using namespace std;
        
        struct Value {};
        
        template <typename T>
        struct greedy_struct {
          static void run(const T&) {
            cout << "const T& (struct)" << endl;
          }
          static void run(T&&) {
            cout << "T&& (struct)" << endl;
          }
        };
        
        // Per Toby's answer.
        template <typename T>
        void greedy_sfinae(const T&) {
          cout << "const T& (sfinae)" << endl;
        }
        
        template <
            typename T,
            typename = std::enable_if_t<std::is_rvalue_reference<T&&>::value>>
        void greedy_sfinae(T&&) {
          cout << "T&& (sfinae)" << endl;
        }
        
        // Bad.
        template <typename T>
        void greedy_sfinae_bad(const T&) {
          cout << "const T& (sfinae bad)" << endl;
        }
        
        template <
            typename T,
            typename = std::enable_if_t<std::is_rvalue_reference<T>::value>>
        void greedy_sfinae_bad(T&&) {
          cout << "T&& (sfinae bad)" << endl;
        }
        
        template <typename TF>
        void greedy(TF&& value) {
          using T = std::decay_t<TF>;
          greedy_struct<T>::run(std::forward<TF>(value));
          greedy_sfinae(std::forward<TF>(value));
          greedy_sfinae_bad(std::forward<TF>(value));
          cout << "---" << endl;
        }
        
        int main() {
          Value x;
          const Value y;
        
          greedy(x);
          greedy(y);
          greedy(Value{});
          greedy(std::move(x));
        
          return 0;
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2014-01-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2014-10-31
          • 1970-01-01
          • 2011-02-14
          相关资源
          最近更新 更多