C++: while (std::cin);
の意味
概要
競技プログラミングなどでC++で標準入力の処理方法を調べていて気になった。以下のようなコードでwhile (std::cin);
で標準入力がなくなるまで (データの終端 EOF (End Of File)を検知するまで) ループして処理したりしていた。
#include <iostream>
#include <string>
int main(void) {
std::string str;
while (std::cin >> str) {
std::cout << str << "\n";
}
return 0;
}
思えば,C++を始めたときはこのstd::cinとstd::coutがC言語と全然違って困惑して始まり,結局わからないところをそのままにして進んできた。
いろいろ知識もついてきて,気になったのでちゃんと調べることにした。調べるにあたって,参照規格にはC++ 2011のオープンアクセス可能なドラフトのN3337を使用する。
std::cin
の定義
まず,std::cinの定義を確認する。規格では「27.4 Standard iostream objects」に以下のようにstd::cinの定義がある。
実際に,Ubuntu 18.04/usr/include/c++/7/iostreamで上記定義を確認できた。
従って,std::cinはistream型のオブジェクトのようだ。さらに,istreamはbasic_istream<char>の別名であることが,「27.7 Formatting and manipulators」の<istream>の定義から分かる。
つまり,std::cinはbasic_istream<char>のインスタンスとみなせばよい。
std::basic_istream
そのままbasic_istreamの定義を見ていく。
「27.7.2 Input streams」で以下のように演算子が定義されている。
namespace std {
template <class charT, class traits = char_traits<charT> >
class basic_istream : virtual public basic_ios<charT,traits> {
public:
// types (inherited from basic_ios ([ios])):
typedef charT char_type;
typedef typename traits::int_type int_type;
typedef typename traits::pos_type pos_type;
typedef typename traits::off_type off_type;
typedef traits traits_type;
// [istream.cons] Constructor/destructor:
explicit basic_istream(basic_streambuf<charT,traits>* sb);
virtual ~basic_istream();
// [istream::sentry] Prefix/suffix:
class sentry;
// [istream.formatted] Formatted input:
basic_istream<charT,traits>& operator>>(
basic_istream<charT,traits>& (*pf)(basic_istream<charT,traits>&));
basic_istream<charT,traits>& operator>>(
basic_ios<charT,traits>& (*pf)(basic_ios<charT,traits>&));
basic_istream<charT,traits>& operator>>(
ios_base& (*pf)(ios_base&));
basic_istream<charT,traits>& operator>>(bool& n);
basic_istream<charT,traits>& operator>>(short& n);
basic_istream<charT,traits>& operator>>(unsigned short& n);
basic_istream<charT,traits>& operator>>(int& n);
basic_istream<charT,traits>& operator>>(unsigned int& n);
basic_istream<charT,traits>& operator>>(long& n);
basic_istream<charT,traits>& operator>>(unsigned long& n);
basic_istream<charT,traits>& operator>>(long long& n);
basic_istream<charT,traits>& operator>>(unsigned long long& n);
basic_istream<charT,traits>& operator>>(float& f);
basic_istream<charT,traits>& operator>>(double& f);
basic_istream<charT,traits>& operator>>(long double& f);
basic_istream<charT,traits>& operator>>(void*& p);
basic_istream<charT,traits>& operator>>(
basic_streambuf<char_type,traits>* sb);
// [istream.unformatted] Unformatted input:
streamsize gcount() const;
int_type get();
basic_istream<charT,traits>& get(char_type& c);
basic_istream<charT,traits>& get(char_type* s, streamsize n);
basic_istream<charT,traits>& get(char_type* s, streamsize n,
char_type delim);
basic_istream<charT,traits>& get(basic_streambuf<char_type,traits>& sb);
basic_istream<charT,traits>& get(basic_streambuf<char_type,traits>& sb,
char_type delim);
basic_istream<charT,traits>& getline(char_type* s, streamsize n);
basic_istream<charT,traits>& getline(char_type* s, streamsize n,
char_type delim);
basic_istream<charT,traits>& ignore(
streamsize n = 1, int_type delim = traits::eof());
int_type peek();
basic_istream<charT,traits>& read (char_type* s, streamsize n);
streamsize readsome(char_type* s, streamsize n);
basic_istream<charT,traits>& putback(char_type c);
basic_istream<charT,traits>& unget();
int sync();
pos_type tellg();
basic_istream<charT,traits>& seekg(pos_type);
basic_istream<charT,traits>& seekg(off_type, ios_base::seekdir);
protected:
basic_istream(const basic_istream& rhs) = delete;
basic_istream(basic_istream&& rhs);
// [istream.assign] Assign/swap:
basic_istream& operator=(const basic_istream& rhs) = delete;
basic_istream& operator=(basic_istream&& rhs);
void swap(basic_istream& rhs);
};
// [istream::extractors] character extraction templates:
template<class charT, class traits>
basic_istream<charT,traits>& operator>>(basic_istream<charT,traits>&,
charT&);
template<class traits>
basic_istream<char,traits>& operator>>(basic_istream<char,traits>&,
unsigned char&);
template<class traits>
basic_istream<char,traits>& operator>>(basic_istream<char,traits>&,
signed char&);
template<class charT, class traits>
basic_istream<charT,traits>& operator>>(basic_istream<charT,traits>&,
charT*);
template<class traits>
basic_istream<char,traits>& operator>>(basic_istream<char,traits>&,
unsigned char*);
template<class traits>
basic_istream<char,traits>& operator>>(basic_istream<char,traits>&,
signed char*);
}
basic_istream<charT,traits>& operator>>(bool& n);
のように,>>
演算子が型ごとにたくさん定義されており,返却値の型がどれもbasic_istream&
となっている。演算子の定義だが,やっていることは関数とほぼ違いがない。
istream (basic_istream) 自身への参照を返してくれているため,以下のような処理の連結が可能となっている。
std::cin >> c1 >> x >> c2 >> y >> c3;
続いて,std::cinをそのまま評価した場合にどうなるかがポイントとなる。そのまま素直に考えると,変数が空かどうかでの判定となってしまうが,これだとデータの終了を検知できない。
もう少し,basic_istreamの説明を読み進めると以下の説明がある。
規格の文章で少々読みにくい。ひとまず翻訳すると以下のとおりとなる。
もう少し,手短に説明する。
>>演算子とその他のgetなどの関数をそれぞれ書式化入力関数と非書式化入力関数とグループ分けしている。これらの関数にはデータ処理の共通呼び出し関数があり,EOFを検知するとsetstate(eofbit)
が呼ばれるとのことだ。
このsetstate()
はbasic_istreamの派生元クラスのbasic_iosで定義されている関数だ (27.5.5.4 basic_iso flags functions)。setstateは中でclear()を読んでいる。このclear()の処理内容がわかりにくい。
「basic_ios::clear – cpprefjp C++日本語リファレンス」も参考にすると,関数を実行するとPostconditionに記載されている状態になる。つまり,rdstate()
にstate
のフラグを追加する。もっというと,setstate
で指定した状態が現在の状態に追加される (規格の書き方で言葉での説明が少なくわかりにくい)。
std::basic_istream
のbool
へのキャスト結果
では,このstateはどこで使われるのか?
basic_iosには他に以下の演算子が上書き定義されている。
条件式などでboolへ型変換時の演算子が定義されている。ここで,fail()を使っており,その中のrdstate()でstateを使っているようだ。
肝心のfail()はrdstateにfailbitかbadbitが存在するとtrueを返す。ただ,この情報だけだとEOFの情報 (eofbit) を使っていない。
念の為以下のコードでwhile (std::cin >> str)
の終端処理後のstateを確認した。
以下のコマンドでコンパイルして実行する。
g++ istream.cpp && ./a.out <<-EOT
LINE1
LINE2
EOT
実行結果は以下のとおりだ。
LINE1 LINE2 rdstate()=6 badbit=1, failbit=4, eofbit=2, goodbit=0
どうやら,データの終端になると,eofbitの他にfailbitも存在している。ここまで読んだbasic_istreamの規格だとこれだと説明がつかない。
std::cin::operator >>
の処理結果
規格を更に読み進める。「27.7.2.2.3 basic_istream::operator>>」に>>の定義の記載がある。
文字列やバッファーが存在しなければ,setstate(failbit)
を実行するときちんと書いてあった。これで説明がついた。
結論
C++でのwhile (std::cin);
の意味について調査した。
整理すると,以下の流れでデータの終端または失敗を判定している。
わざわざ規格に当たらなくても,C++ ポケットリファレンスの「p. 249 入力の終わり (EOF) を判定する」と「p. 254 ストリームのエラーを知る」を読めば分かることだった。ポケットリファレンスは持っているのだが,規格で調べ終わってから記述に気付いてしまい,後の祭りだった。
C++ポケットリファレンスのSTLの関数定義などの記述がいまいち小難しく見えて,避けていたのがここにきて仇となった。もう少し活用していきたい。
今回の調査で,std::cin
の実態がstd::basic_istream
であることを初めて知った。その他,キャストのオーバーライドなどやっていることを初めて知った。C++はいちいち細かいところの調査に規格をあちこち探す必要があり,いちいち時間がかかる。今回の調査も4-5時間かかった。割り切りも必要だが,重要なところなどはきっちり調べて正確な知識を身につけていきたい。