44

私はネットワークライブラリを作成しており、ファイル記述子の所有権を処理するために移動セマンティクスを多用しています。私のクラスの1つは、他の種類のファイル記述子ラッパーを受け取り、所有権を取得したいと考えているので、次のようになります。

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に渡し、後でその左辺値(クリア済み)を使用することによって、自分自身を一度撃ちました。

だからここに質問があります:テンプレートを右辺値参照にのみバインドする方法は?

4

5 に答える 5

44

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また、ファイル記述子ラッパーのみを受け入れるように、何らかの制限を追加することもお勧めします。

于 2011-10-23T00:51:04.583 に答える
14

簡単な方法は、左辺値参照を受け入れる削除されたメンバーを提供することです。

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

これは、左辺値引数に常に適しています。


いくつかの引数を取る関数があり、それらはすべて右辺値である必要がある場合、いくつかの削除された関数が必要になります。この状況では、SFINAEを使用して、左辺値引数から関数を非表示にすることをお勧めします。

これを行う1つの方法は、C++17とConceptsTSを使用することです。

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

-fconcepts注:GCC 6.1では、コンパイラーのコア部分ではなく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
}
于 2016-10-03T09:07:52.213 に答える
6

私は人々を混乱させるように思われることをかなり頻繁に学びました:SFINAEを使用することは問題ありませんが、私は使用できません:

std::is_rvalue_reference<T>::value

私が望むようにそれが機能する唯一の方法は

!std::is_lvalue_reference<T>::value

理由は次のとおりです。右辺値参照ではなく、右辺値を受け取る関数が必要です。で条件付きで有効にされた関数は、右辺値を受け取りませんが、右辺値参照を受け取ります。std::is_rvalue_reference<T>::value

于 2011-10-23T12:13:39.660 に答える
2

左辺値参照の場合、Tは左辺値参照であると推定され、右辺値参照の場合、Tは非参照であると推定されます。

したがって、関数が右辺値参照にバインドする場合、特定のタイプTのコンパイラーによって最後に表示されるのは次のとおりです。

std::is_rvalue_reference<T>::value

ではなく

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

于 2016-09-30T14:30:22.963 に答える
0

残念ながら、とを区別するオーバーロードを実際に作成しようとしている場合(たとえば、一方と他方の両方で使用)、試してみるとis_rvalue_reference<TF>(完全に転送されたタイプはどこにTFありますか)うまく機能しないようです。const T&T&&enable_ifis_rvalue_reference_v<TF>!is_rvalue_reference_V<TF>

解決策(ハッキーではありますが)は、転送されたものを減衰させてからT、これらのタイプを認識しているコンテナーにオーバーロードを配置することです。この例を生成しました:

うーん、私は間違っていました。Tobyの答え(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;
}
于 2018-01-04T21:24:09.353 に答える