C ++ 11は素晴らしいです。おそらく(私の意見では)最も美しい機能の1つは、いわゆる範囲ベースのforループです。
for ( std::size_t i(0); i < range.size(); ++i ) { // do something to range[i]; }
または
for ( Range::iterator it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
の代わりに、またはC ++ 11自動で簡略化:
for ( auto it(range.begin()); it != range.end(); ++it ) { // do something to *it; }
これは次のように言えます:
for ( auto& entry : range ) { // do something with entry. }
これは非常に表現力豊かです:話します範囲内のi番目の位置やエントリを指すイテレータではなく、エントリについて。ただし、この構文には、サブ範囲を持つ機能がありません(たとえば、最後のエントリを無視します)。一連の小さなヘルパー構造体/メソッドで、この機能をクリーンな方法で追加したいと思います。
モチベーションはこれだけです;-)さて、実際の取引です。
この投稿では、range
のエントリの条件について説明します。基本的に、ヘルパーコードは
for ( auto& entry : range ) { if ( condition(entry) ) { // do something to entry. } }
と同等の機能を実行する必要がありますが、そのレベルのインデントはありません。
ConditionalRangeのコードは次のとおりです。
template <typename Range, typename Runnable> ConditionalRange<Range, Runnable> makeConditionalRange(Range& range, Runnable&& condition) { static_assert(std::is_same<decltype(condition(*std::declval<Range>().begin())), bool>::value, "Condition must return a boolean value."); return ConditionalRange<Range, Runnable>(range, std::forward<Runnable>(condition)); } template <typename Range, typename Runnable> struct ConditionalRange { public: friend ConditionalRange makeConditionalRange<>(Range&, Runnable&&); public: using iterator_type = ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>; iterator_type begin() const { auto b = range.begin(); while ( b != range.end() && !condition(*b) ) { ++b; } return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(b, range.end(), condition); } iterator_type end() const { return ConditionalIterator<decltype(std::declval<Range>().begin()), Runnable>(range.end(), range.end(), condition); } private: ConditionalRange(Range& range_, Runnable&& condition_) : range(range_), condition(std::forward<Runnable>(condition_)) { } private: Range& range; Runnable condition; };
これはイテレータのヘルパー構造体です:
template <typename Iterator, typename Runnable> struct ConditionalIterator { private: Iterator iterator; Iterator end; const Runnable& condition; public: ConditionalIterator(Iterator b, Iterator e, const Runnable& r) : iterator(b), end(e), condition(r) { } auto operator*() -> decltype(*iterator) { return *iterator; } ConditionalIterator& operator++() { do { ++iterator; } while ( iterator != end && !condition(*iterator) ); return *this; } bool operator==(const ConditionalIterator& second) { return iterator == second.iterator; } bool operator!=(const ConditionalIterator& second) { return !(*this == second); } };
使用目的は:
std::vector<int> ns{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; for ( const auto& n : makeConditionalRange(ns, [](int a) { return a % 2 == 0; }) ) { std::cout << n << " "; } std::cout << "\n";
内容私のアプローチの弱点はありますか?完璧な世界ではいけないのに、どのような議論で失敗するのでしょうか?
コメント
回答
中括弧を使用して関数から戻ることができます。そうすれば、長くて面倒なタイプ(DRY、可能な限り)を繰り返す必要がなくなります。たとえば、ConditionalRange::end
:
iterator_type end() const { return { range.end(), range.end(), condition }; }
また、関数がCスタイルの配列も受け入れるようにしたい場合があります。したがって、std::begin
とstd::end
メンバー関数を呼び出す代わりに:
iterator_type end() const { return { std::end(range), std::end(range), condition }; }
これ(コード内のすべての場所)といくつかの些細な変更により、コードはC-でも機能します。スタイル配列(ここでテスト済みで正常に動作します)。
関数の名前付け
名前makeConditionalRange
はかなり長いです。作成した関数は、filter
という名前で他のライブラリやプログラミング言語(Pythonが思い浮かびます)にすでに存在します。検討する必要があります。多くのユーザーが一目で名前を認識できるように名前を変更します(さらに、名前は短くなります)。
for(const auto& n: iterate_if(ns, [](int a) { return a % 2 == 0; }) )
filter_iterator
?operator!=
これをoperator==