ゆとりーなの日記

日記的な事を書いて行くと思はれる

Boostがまさか・・・

時代はRangeです。入力ストリームからのコピーもRangeにやりたいものです。Boost.Rangeにはistream_rangeというものがあります。名前的にstd::istream_iteratorな雰囲気が漂っています。一方でstd::istreambuf_iteratorというものもあります。こちらのイテレータを使ったほうが効率が良くなる場面というのがあります。しかしistreambuf_rangeという感じのRangeは見つかりません。この位であればすぐ作れるだろうと思って適当に作りました。

#include <iostream>
#include <iterator>
#include <boost/range/iterator_range.hpp>

template <typename Elem, typename Traits>
boost::iterator_range<std::istreambuf_iterator<Elem, Traits>> istreambuf_range(std::basic_istream<Elem, Traits>& in) {
  typedef std::istreambuf_iterator<Elem, Traits> iterator_type;
  typedef boost::iterator_range<iterator_type> result_type;
  return result_type(iterator_type(in), iterator_type());
}

ところがこれ、実際に使ってみるとコンパイルに失敗します。

#include <vector>
#include <boost/range/algorithm/copy.hpp>
 
int main() {
  std::vector<char> v;
  boost::copy(istreambuf_range<char>(std::cin), std::back_inserter(v)); // コンパイルエラー
  return 0;
}

エラーを見ると、コンセプトチェックのところで落ちていることが分かります。boost/range/concepts.hpp(157)で「'初期化中' : 'char' から 'char &' に変換できません。」と言っていますのでそこの部分を見てます。

BOOST_DEDUCED_TYPENAME boost::detail::iterator_traits<Iterator>::reference r1(*i);

BOOST_DEDUCED_TYPENAMEはここでは実質typenameのdefineで、恐らく環境によってはtypename付け関連でコンパイルが通ったり通らなかったりするのでその対策だと思います。
boost::detail::iterator_traitsは実質std::iterator_traitsで、::referenceは
Iteratorがstd::istreambuf_iterator>なのでこれのreferenceとなり、これはchar &です。一方でiもstd::istreambuf_iterator>で、これのoperator*はcharを返すのでこのコードは実質、

char &r1 = char();

とやっているに等しく、参照は当然rvalueを取れないのでこの部分で転ぶことになります。
実質このコンセプトは、イテレータの参照をはがしたものはイテレータが保持する型の参照で受けられるってことを要求しているっぽいのですが、そうすると値で返すイテレータが全滅する気がします。そうするとこのコンセプトが合っているのかということが問題になると思うのですが、残念ながらそこまではよく分かりません。後は詳しい人に丸投げです。はい。
因みにこれ、Boost1.46からの問題で、それ以前のバージョンでは問題なくコンパイルが通るみたいです。
追記:
Boost1.46.1が出ましたが、こちらでもコンパイルは通りませんでした。
更に追記:
MB先生のコメントにより原因が判明しました。結論から言うとBoost側のバグではありませんでした。
0xになってしまうのですがn3242のstd::istreambuf_iteratorのところを見ると、本来であれば

template<class charT, class traits = char_traits<charT>>
class istreambuf_iterator : public iterator<input_iterator_tag, charT,
typename traits::off_type, unspecified , charT> { ...

となっているはずの部分がVC++2010では

template<class charT, class traits> // デフォルトテンプレート引数はVC++2010ではまだ駄目なので
class istreambuf_iterator : public iterator<input_iterator_tag, charT,
typename traits::off_type, charT* , charT&> { ...

となっていました。違いはというと、istreambuf_iteratorが継承しているiteratorのテンプレート第五引数、これがreferenceの型になるわけですが、正しくはこれはCharTであるべきはずのところが、VC++2010ではChar&になってしまっていることが原因のようです。因って正しい実装であれば、問題のコンセプトの部分は、

char r1 = char();

相当に展開されるので何も問題はなくなります。
名前がreferenceなだけに、一見イテレータが指す型の参照を返しそうなものなので全然気づけませんでした。どちらかというとreferenceは参照というよりは、参照をはがした時に取れる型という認識でいた方がいいみたいですね。