11

入力ストリームを反復するには、通常、次のstd::istream_iteratorように使用します。

typedef std::istream_iterator<std::string> input_iterator;

std::ifstream file("myfile");
for (input_iterator i(file); i != input_iterator(); i++) {
  // Here, *i denotes each element extracted from the file
}

for範囲ベースのステートメントを使用して、入力ストリームを反復処理できればいいのですが。ただし、クラス型のオブジェクトの場合、範囲ベースforでは、オブジェクトがメンバー関数を持つ必要がありbegin()ますend()(§6.5.4、太字の強調が追加されています)。

  • _RangeTが配列型の場合、 begin-exprend-exprはそれぞれ と です。ここで、__rangeはバインドされた配列です。が不明なサイズの配列または不完全な型の配列である場合、プログラムの形式が正しくありません。__range + __bound__bound_RangeT

  • _RangeTがクラス typeである場合unqualified-ids beginおよびendは、クラス メンバ アクセス ルックアップ (3.4.5) によるかのように class のスコープ内で_RangeTルックアップされ、いずれか (または両方) が少なくとも 1 つの宣言、begin-exprおよびendを見つけた場合-exprはそれぞれ と です__range.begin()__range.end()

  • それ以外の場合、begin-exprend-exprはそれぞれ と であり、begin(__range)とは引数依存の検索 (3.4.2) で検索されます。この名前検索の目的で、namespaceは関連付けられた名前空間です。end(__range)beginendstd

入力ストリームにはこれらのメンバー関数がない (コンテナーではない) ため、範囲ベースは機能forしません。とにかく、抽出するタイプを指定する何らかの方法が必要になるため、これは理にかなっています (std::string上記の場合)。

しかし、抽出したいものがわかっている場合、入力ストリームに対して独自のbegin()andend()関数 (おそらく and の特殊化またはオーバーロードstd::begin())を定義std::end()して、上記のようにクラス メンバー アクセス ルックアップによって検出されるようにすることは可能でしょうか?

前のルックアップが失敗した場合、関数が引数依存のルックアップでルックアップされるかどうかは、§6.5.4 から (少なくとも私には) 不明です。考慮すべきもう1つのことは、およびその派生物には、シークのフラグであるstd::ios_baseというメンバーが既にあるということです。end

意図した結果は次のとおりです。

std::ifstream file("myfile");
for (const std::string& str : file) {
  // Here, str denotes each element extracted from the file
}

または:

std::ifstream file("myfile");
for (auto i = begin(file); i != end(file); i++) {
  // Here, *i denotes each element extracted from the file
}
4

4 に答える 4

7

明らかなアプローチは、タイプと必要なインターフェースを提供する単純なデコレーターをストリームに使用することです。これは次のようになります。

template <typename T>
struct irange
{
    irange(std::istream& in): d_in(in) {}
    std::istream& d_in;
};
template <typename T>
std::istream_iterator<T> begin(irange<T> r) {
    return std::istream_iterator<T>(r.d_in);
}
template <typename T>
std::istream_iterator<T> end(irange<T>) {
    return std::istream_iterator<T>();
}

for (auto const& x: irange<std::string>(std::ifstream("file") >> std::skipws)) {
    ...
}
于 2012-10-23T19:32:55.137 に答える
1

std名前空間にクラスと関数の特殊化を配置できるため、引数に依存するルックアップで検出されるかどうかは関係ありません。

于 2012-10-23T19:15:34.857 に答える
1

ここに考えられる解決策の 1 つがあります。残念ながら、追加の構造が必要です。

#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <string>

struct S {
  std::istream& is;
  typedef std::istream_iterator<std::string> It;
  S(std::istream& is) : is(is) {}
  It begin() { return It(is); }
  It end() { return It(); }
};

int main () {
  std::ifstream file("myfile");
  for(auto& string : S(file)) {
    std::cout << string << "\n";
  }
}

別の解決策は、次から派生することですstd::ifstream

#include <iostream>
#include <fstream>
#include <iterator>
#include <algorithm>
#include <string>


struct ifstream : std::ifstream {
  // using std::ifstream::ifstream; I wish g++4.7 supported inheriting constructors!
  ifstream(const char* fn) : std::ifstream(fn) {}
  typedef std::istream_iterator<std::string> It;
  It begin() { return It(*this); }
  It end() { return It(); }
};

int main () {
  ifstream file("myfile");
  for(auto& string : file) {
    std::cout << string << "\n";
  }
}
于 2012-10-23T19:26:46.830 に答える
1

std::begin私は専門化のstd::endアイデアを追求しようとしましたstd::basic_istream(私はこのテンプレートメタプログラミングビジネスが得意ではありません):

namespace std
{
  template <typename C>
  typename
  std::enable_if<
    std::is_base_of<std::basic_istream<typename C::char_type>, C>::value,
    std::istream_iterator<std::string>>::type
  begin(C& c)
  {
    return {c};
  }

  template <typename C>
  typename
  std::enable_if<
    std::is_base_of<std::basic_istream<typename C::char_type>, C>::value,
    std::istream_iterator<std::string>>::type
  end(C& c)
  {
    return {};
  }
}

実際、それはかなりうまく機能します。const C&const ストリームから抽出するのは意味がないと思うので、かかるバージョンを作成しませんでした (そうしようとしたときにエラーが発生しました)。また、これをより動きやすいものにできるかどうかもわかりません。だから今、私はそのmyfileような内容を印刷することができます::

std::ifstream file("myfile");
std::copy(begin(file), end(file), std::ostream_iterator<std::string>(std::cout, " "));

したがって、これらbeginend関数は期待どおりに機能します。forただし、範囲ベースのループで使用するとフラットになります。クラスは、すでに呼び出されたメンバーを持ってstd::basic_istreamいる派生元です(これは、ストリーム内でシークするためのフラグです)。範囲ベースのループがこれを見つけると、対応するものが見つからないため、あきらめます(言うまでもなく、適切な種類のエンティティではありません)。std::ios_baseendforbeginend

main.cpp:35:33: エラー: タイプ 'std::basic_ifstream' の範囲ベースの 'for' 式には 'end' メンバーがありますが、'begin' はありません</p>

他の人が述べたように、両方の状況で機能する唯一の代替手段は、ラッパー オブジェクトを作成することです。残念ながら、そのendメンバーは、std::ios_baseこれを適切に実装する可能性を完全に台無しにします。

于 2012-10-23T21:44:36.610 に答える