簡體   English   中英

如何使模板右值引用參數僅綁定到右值引用?

[英]How to make template rvalue reference parameter ONLY bind to rvalue reference?

我正在編寫一個網絡庫並大量使用移動語義來處理文件描述符的所有權。 我的 class 之一希望接收其他類型的文件描述符包裝器並獲得所有權,所以它類似於

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

它必須處理多個不相關的類型,因此 receive_ownership 必須是一個模板,並且為了安全起見,我希望它只綁定到右值引用,以便用戶在傳遞左值時必須顯式地 state std::move。

receive_ownership(std::move(some_lvalue));

但問題是:C++ 模板推導允許傳入一個左值而不需要額外的努力。 實際上,我不小心將一個左值傳遞給了receive_ownership,並在以后使用該左值(清除),實際上是我自己開槍打死了自己。

所以這里有一個問題:如何使模板只綁定到右值引用?

您可以將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添加某種限制,使其僅接受文件描述符包裝,這也是一個好主意。

一種簡單的方法是提供一個接受左值引用的已刪除成員

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
}

我學到了一些似乎經常使人困惑的東西:使用SFINAE是可以的,但是我不能使用:

std::is_rvalue_reference<T>::value

我想要的唯一方法是

!std::is_lvalue_reference<T>::value

原因是:我需要函數接收右值 ,而不是右值引用 有條件啟用std::is_rvalue_reference<T>::value將不會收到rvalue,而是會收到rvalue引用。

對於左值引用,T推導為左值引用,對於右值引用,T推導為非參考。

因此,如果函數綁定到右值引用,則對於特定類型T,編譯器最后看到的是:

std::is_rvalue_reference<T>::value

並不是

std::is_rvalue_reference<T&&>::value

不幸的是,如果您實際上試圖進行區分const T&T&&重載(例如,在兩者中都使用enable_if ,其中一個與is_rvalue_reference_v<TF>一起使用),那么嘗試is_rvalue_reference<TF> (其中TF是完美轉發的類型)似乎效果is_rvalue_reference_v<TF> ,另一個帶有!is_rvalue_reference_V<TF> )。

一個解決方案(盡管很hacky)是衰減轉發的T ,然后將重載放置在知道這些類型的容器中。 產生了這個例子

嘿,我錯了,只是忘了看托比的答案( is_rvalue_reference<TF&&> )-盡管您可以執行std::forward<TF>(...)令人困惑,但是我想這就是為什么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;
}

使用更現代的 C++,我們可以簡單地require T&&是一個右值引用:

#include <type_traits>

template<typename T> requires std::is_rvalue_reference_v<T&&>
void receive_ownership(T&&)
{
    // taking file descriptor of t, and clear t
}

簡單演示:

#include <string>
#include <utility>
int main()
{
    auto a = std::string{};
    auto const b = a;

    receive_ownership(a);            // ERROR
    receive_ownership(std::move(a)); // okay

    receive_ownership(b);            // ERROR
    receive_ownership(std::move(b)); // okay - but unwise!

    receive_ownership(std::string{}); // okay
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM