web-dev-qa-db-ja.com

左辺値と右辺値の両方の引数を受け入れる関数

テンプレートにせずに、左辺値と右辺値の両方の引数を受け入れる関数をC++で作成する方法はありますか?

たとえば、istreamから読み取って画面に読み取られたデータなどを出力する関数print_streamを作成するとします。

このようにprint_streamを呼び出すのは合理的だと思います。

fstream file{"filename"};
print_stream(file);

同様にこのように:

print_stream(fstream{"filename"});

しかし、両方が機能するようにprint_streamを宣言するにはどうすればよいですか?

私がそれを宣言した場合

void print_stream(istream& is);

次に、右辺値が非const左辺値参照にバインドされないため、2番目の使用はコンパイルされません。

私がそれを宣言した場合

void print_stream(istream&& is);

その場合、左辺値は右辺値参照にバインドされないため、最初の使用はコンパイルされません。

私がそれを宣言した場合

void print_stream(const istream& is);

const istreamから読み取ることができないため、関数の実装はコンパイルされません。

関数をテンプレートにして「ユニバーサルリファレンス」を使用することはできません。その実装を個別にコンパイルする必要があるためです。

2つのオーバーロードを提供できます。

void print_stream(istream& is);
void print_stream(istream&& is);

2番目の呼び出しを最初に実行しますが、これは多くの不要な定型文のように思われます。このようなセマンティクスを持つ関数を作成するたびに、それを実行しなければならないのは非常に残念です。

私にできるもっと良いことはありますか?

34
HighCommander4

2つのオーバーロードを提供するか、関数をテンプレートにする以外に、saneの選択肢はあまりありません。

本当に、本当に(醜い)代替が必要な場合、あなたができる唯一の(非常識な)ことは、関数にconst&を受け入れさせることだと思います。 const修飾型のオブジェクトを渡すことはできません(とにかくそれをサポートしたくありません)。次に、関数は参照のconstnessをキャストすることができます。

しかし、私は個人的に2つのオーバーロードを記述し、一方を他方の観点から定義するので、宣言を複製しますが、定義は複製しません。

void foo(X& x) 
{ 
    // Here goes the stuff... 
}

void foo(X&& x) { foo(x); }
20
Andy Prowl

もう1つのかなり醜い代替手段は、関数をテンプレートにして、両方のバージョンを明示的にインスタンス化することです。

template<typename T>
void print(T&&) { /* ... */ }

template void print<istream&>(istream&);
template void print<istream&&>(istream&&);

これは個別にコンパイルできます。クライアントコードは、テンプレートの宣言のみを必要とします。

でも、個人的にはAndyProwlの提案に固執したいと思います。

6
jrok

太字にする、一般的なフォワード関数を採用し、それらに適切な名前を付けます。

template<typename Stream>
auto stream_meh_to(Stream&& s) 
->decltype(std::forward<Stream>(s) << std::string{/*   */}){
    return std::forward<Stream>(s) << std::string{"meh\n"};}

これは、ostreamsだけでなく、機能するのに意味のあるものなら何でも機能することに注意してください。それは良いことです。

関数が意味をなさない引数で呼び出された場合、関数はこの定義を単に無視します。ちなみに、これはインデントが4スペースに設定されている場合にうまく機能します。 :)


これはCubeの答えと同じですが、可能であれば、特定の型をチェックしてジェネリックプログラミングをnotしない方がエレガントだと言っている点が異なります。そのことをしなさい。

5
alfC
// Because of universal reference
// template function with && can catch rvalue and lvalue 
// We can use std::is_same to restrict T must be istream
// it's an alternative choice, and i think is's better than two overload functions
template <typename T>
typename std::enable_if<
  std::is_same<typename std::decay<T>::type, istream>::value
>::type
print(T&& t) {
  // you can get the real value type by forward
  // std::forward<T>(t)
}
4
Cube

関数が関数の引数の所有権を取得することを期待する場合、引数を値として入力してから移動する傾向があります。引数の移動にコストがかかる場合(std :: arrayなど)、これは望ましくありません。

典型的な例は、オブジェクトの文字列メンバーを設定することです。

class Foo {
   private:
      std::string name;
   public:
      void set_name( std::string new_name ) { name = std::move(new_name); }
};

この関数の定義により、文字列オブジェクトのコピーなしでsetnameを呼び出すことができます。

Foo foo;
foo.set_name( std::string("John Doe") );
// or
std::string tmp_name("Jane Doe");
foo.set_name( std::move(tmp_name) );

ただし、元の値の所有権を保持したい場合は、コピーを作成できます。

std::string name_to_keep("John Doe");
foo.set_name( name_to_keep );

この最後のバージョンは、const参照を渡してコピーを割り当てるのと非常によく似た動作をします。

class Foo {
   // ...
   public:
      void set_name( const std::string& new_name ) { name = new_name; }
};

これは、コンストラクターにとって特に便利です。

0
Jorge Bellon