74

私はこの署名を持つサードパーティ関数を持っています:

std::vector<T> f(T t);

また、名前付きの既存の潜在的に無限の範囲 ( range-v3 sort ) があります。その範囲のすべての要素にマップし、すべてのベクトルをすべての要素を含む単一の範囲にフラット化するパイプラインを作成したいと考えています。Tsrcf

本能的に、私は次のように書きます。

 auto rng = src | view::transform(f) | view::join;

ただし、一時コンテナーのビューを作成できないため、これは機能しません。

range-v3 はこのような範囲パイプラインをどのようにサポートしていますか?

4

6 に答える 6

18

これを正しく行う方法を示すテスト ケースが range-v3 ライブラリにあるようです。オペレーターをパイプラインに追加する必要があります。views::cache1

auto rng = views::iota(0,4)
        | views::transform([](int i) {return std::string(i, char('a'+i));})
        | views::cache1
        | views::join('-');
check_equal(rng, {'-','b','-','c','c','-','d','d','d'});
CPP_assert(input_range<decltype(rng)>);
CPP_assert(!range<const decltype(rng)>);
CPP_assert(!forward_range<decltype(rng)>);
CPP_assert(!common_range<decltype(rng)>);

OPの質問に対する解決策は、

auto rng = src | views::transform(f) | views::cache1 | views::join;
于 2020-02-26T14:01:10.467 に答える
12

できないのではないかと思います。いずれのviews にも、一時的なものをどこにでも保存する機構がありません。これは、docsからのビューの概念に明示的に反しています。

ビューは、基になる一連の要素のビューを変更またはコピーせずに独自の方法で表示する軽量のラッパーです。ビューは安価に作成およびコピーでき、非所有参照セマンティクスを備えています。

それが機能し、表現を存続させるためにはjoin、どこかでそれらの一時的なものを保持する必要があります。その何かがaction. これは機能します(demo):

auto rng = src | view::transform(f) | action::join;

明らかsrcに無限ではないことを除いて、そして有限であっても、srcとにかく使用したくないオーバーヘッドが多すぎる可能性があります。

左辺値コンテナーを要求する(そしてイテレーターのペアを返す)view::join代わりに、内部に格納する右辺値コンテナーを許可する (そして返すその格納されたバージョンへのイテレータ ペア)。しかし、それはコードをコピーするのに数百行に相当するので、たとえそれが機能したとしても、かなり物足りないようです。view::all

于 2016-05-01T21:25:23.233 に答える
4

もちろん、ここでの問題は、ビューの全体的な考え方、つまり非格納の階層化された遅延評価器です。この契約に遅れずについていくために、ビューは範囲要素への参照を渡す必要があり、一般に右辺値と左辺値の両方の参照を処理できます。

残念ながら、この特定のケースでは、関数がコンテナーを値で返し、ビュー ( ) を内部コンテナーにバインドしようとするときに左辺値を期待するview::transformため、右辺値参照しか提供できません。f(T t)view::joinview::all

考えられる解決策はすべて、パイプラインのどこかにある種の一時ストレージを導入することです。私が思いついたオプションは次のとおりです。

  • view::all右辺値参照によって渡されたコンテナーを内部的に格納できるバージョンを作成します(Barry の提案による)。私の観点からは、これは「非保存ビュー」の概念に違反しており、面倒なテンプレート コーディングも必要になるため、このオプションに反対することをお勧めします。
  • view::transformステップ後の中間状態全体に一時的なコンテナーを使用します。手動で行うことができます:

    auto rng1 = src | view::transform(f)
    vector<vector<T>> temp = rng1;
    auto rng = temp | view::join;
    

    または使用しaction::joinます。これは「早期評価」になり、infinitesrcでは機能せず、メモリを浪費し、全体的に元の意図とはまったく異なるセマンティクスを持つため、ほとんど解決策にはなりませんが、少なくともビュークラスの契約に準拠しています.

  • に渡す関数を一時ストレージでラップしますview::transform。最も簡単な例は

    const std::vector<T>& f_store(const T& t)
    {
      static std::vector<T> temp;
      temp = f(t);
      return temp;
    }
    

    に渡しf_storeますview::transformf_store左辺値参照を返すので、view::join今は文句を言いません。

    もちろん、これは多少のハックであり、範囲全体を出力コンテナーなどのシンクに合理化する場合にのみ機能します。view::replaceまたはそれ以上の s のようないくつかの単純な変換には耐えられると思いますが、より複雑なものは、単純でない順序でこのストレージにview::transformアクセスしようとする可能性があります。temp

    その場合、他のタイプのストレージを使用できます。たとえば、std::mapその問題を修正しsrc、いくらかのメモリを犠牲にして無限の遅延評価を許可します。

    const std::vector<T>& fc(const T& t)
    {
        static std::map<T, vector<T>> smap;
        smap[t] = f(t);
        return smap[t];
    }
    

    f関数がステートレスである場合、これをstd::map使用して一部の呼び出しを潜在的に節約することもできます。このアプローチは、要素が不要になることを保証し、std::mapメモリを節約するために要素を削除する方法があれば、さらに改善される可能性があります。ただし、これはパイプラインの以降のステップと評価に依存します。

これらの 3 つの解決策は、 と の間の一時的なストレージを導入するすべての場所をほぼカバーしているためview::transformview::joinこれらがすべてのオプションであると思います。全体的なセマンティクスをそのまま維持でき、実装が非常に簡単であるため、#3 を使用することをお勧めします。

于 2016-09-19T12:27:30.193 に答える