range-based forのrange_traits派が弱いのでなんとかしたい
いや、別に規格が使わないって言ったら私は従いますよ。みなさんがADL使ってるように。
で、私がよく言っているprvalueのlifetimeの問題ってやつなんだけど、コア言語だからlifetimeは考慮されてるって言われてもrange-based forだけ特別扱いされてもねぇ... って気がする。ちなみに私がざっと眺めた限りn3242では発見できなかったので、見つけた人は私に当て付けがましく見せつけて私の心をへし折ってください。
ちなみにcopy/move可能なrangeであればadaptorで問題ありません。ってかメンバにbegin/endを持ってないやつをrange-based forに突っ込むなよってのがみんなの考えとして共通だと私は信じています。
で、copy/moveすら出来ない意味のワカラナイ腐ったオレオレrangeが突っ込まれた場合、こうなるんじゃね?っていう例。まぁそんなコンテナ作ったやつは首吊ってください。
で、共通事項として暗黙のうちに以下のコードが定義されるとしてください。あっconstとかめんどいんで省略です。
#include <iostream> struct fuckin_S { fuckin_S() { std::cout << "fuckin' ctor" << std::endl; } ~fuckin_S() { std::cout << "fuckin' dtor" << std::endl; } fuckin_S( const fuckin_S & ) = delete; fuckin_S( fuckin_S && ) = delete; int * fuckin_begin() { return 0; } int * fuckin_end() { return fuckin_begin() + 1; } }; namespace associated_std { template < typename T > struct range_traits { auto begin( T &t ) -> decltype( t.begin() ) { return t.begin(); } auto end( T &t ) -> decltype( t.end() ) { return t.end(); } }; }
誰が見てもfuckin'ですね。associated_stdってのはstdの代わりです。
これのアダプタ版は私の腐った脳だとこういうふうになるはず。
#include <iostream> using namespace std; struct Adaptor { fuckin_S &ref; Adaptor( fuckin_S &&ref ) : ref( ref ) {} auto begin() -> decltype( ref.fuckin_begin() ) { return ref.fuckin_begin(); } auto end() -> decltype( ref.fuckin_end() ) { return ref.fuckin_end(); } }; int main() { // for ( auto &elem : Adaptor( fuckin_S{} ) ) { auto &&_range = Adaptor( fuckin_S{} ); for ( auto _itr = _range.begin(), _end = _range.end(); _itr != _end; ++_itr ) { // auto &elem = *_itr; // nullのderefはNGなのでコメントアウト cout << "!?" << endl; } } }
とりあえずn3242の展開式にそのまま当てはめました。これの実行結果は
fuckin' ctor fuckin' dtor !?
となります。これはまずいんじゃない?ってことがいいたいんだけど、コア言語だから問題ないらしい...つまりprvalueのlifetimeがなぜか延長されるんだけど、ifとかwhileとかswitchは延長されないっていうことだよね。あんまり例外欲しくない...
で、やってきましたrange_traits派。これは標準のをそもまま使う限りメンバ関数派の方と動作は変わりません。で、このファッキンに合わせようとすると、
#include <iostream> #include <type_traits> using namespace std; namespace associated_std { template <> struct range_traits< fuckin_S > { auto begin( fuckin_S &t ) -> decltype( t.fuckin_begin() ) { return t.fuckin_begin(); } auto end( fuckin_S &t ) -> decltype( t.fuckin_end() ) { return t.fuckin_end(); } }; } int main() { // for ( auto &elem : fuckin_S{} ) { auto &&_range = fuckin_S{}; using namespace associated_std; range_traits< remove_reference< decltype( _range ) >::type > _traits; for ( auto _itr = _traits.begin( _range ), _end = _traits.end( _range ); _itr != _end; ++_itr ) { // auto &elem = *_itr; // nullのderefはNGなのでコメントアウト cout << "It seems good!" << endl; } } }
で、出力は
fuckin' ctor It seems good! fuckin' dtor
となります。これは今までのlifetimeと同じなので例外なくていいねーって言うわけです。
が、range_traitsにも弱点があって、decltype( _range )のところでremove_referenceしてますが、テンプレートの特殊化はそこら辺も見てしまうのでこうするしか無いです。もっと言えばremove_cvとかもつけて、range_traitsの特殊化でconst用のメンバを定義したり...
もしかしたらデフォルトのrange_traitsでqualifiersを削除するようになってるかもしれないですが、どちらにしろqualifiers周りがちょっとよろしくない気配。多分怠け者の私はメンバをテンプレートにして推論させますがテンプレートを特殊化したにもかかわらず結局テンプレートかよとか言われそうで悲しい。
あっ、ちなみにADL推進派の皆さん!私も最初はADLでいいとか思ったけど、やっぱり議論する価値もないですよ!