3

TL;DR

以下の最後のコメント行ブロックの何が問題になっていますか?

// headers and definitions are in the down the question
int main() {

    std::vector<int> v{10,20,30};

    using type_of_temp = std::vector<std::pair<std::vector<int>,int>>;

    // seems to work, I think it does work
    auto temp = copy_range<type_of_temp>(v | indexed(0)
                                           | transformed(complex_keep_index));
    auto w = temp | transformed(distribute);
    print(w);

    // shows undefined behavior
    //auto z = v | indexed(0)
    //           | transformed(complex_keep_index)
    //           | transformed(distribute);
    //print(z);
}

または、言い換えれば、パイプv | indexed(0)transformed(complex_keep_index)明確に定義されたものにするのに、パイプv | indexed(0) | transformed(complex_keep_index)transformed(distribute)未定義の動作にするのは何ですか?

拡張版

私は物事の容器を持っています、

std::vector<int> v{10,20,30};

そして、それらのそれぞれから別のコンテナを生成する関数があります。

// this is in general a computation of type
//      T -> std::vector<U>
constexpr auto complex_comput = [](auto const& x){
    return std::vector{x,x+1,x+2}; // in general the number of elements changes
};

したがって、 to を適用するcomplex_computv、次のようになります。

{{10, 11, 12}, {20, 21, 22}, {30, 31, 32}}

結果も連結すると、最終的に次のようになります。

{10, 11, 12, 20, 21, 22, 30, 31, 32}

ただし、結果が次のようにエンコードされるように、各数値の元のインデックスを追跡したいと考えています。

0 10
0 11
0 12
1 20
1 21
1 22
2 30
2 31
2 32

これを達成するために、私は(最終的に)このソリューションを思いつきました.Boostの範囲を利用しようとしました. 具体的には、次のことを行います。

  1. boost::adaptors::indexedの各要素にインデックスを付けるために使用しますv
  2. 得られた各「ペア」を に変換し、を に適用した結果を にstd::pair格納します。indexcomplex_computvalue
  3. そして最後にそれぞれを に変換std::pair<st::vector<int>,int>std::vector<std::pair<int,int>>ます。

std::vectorただし、 2 つの変換の間に「true」ヘルパーを使用して、2 と 3 の間の範囲をあきらめなければなりませんでした。

#include <boost/range/adaptor/indexed.hpp>
#include <boost/range/adaptor/transformed.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <iostream>
#include <utility>
#include <vector>

using boost::adaptors::indexed;
using boost::adaptors::transformed;
using boost::copy_range;

constexpr auto complex_comput = [](auto const& x){
// this is in general a computation of type
//      T -> std::vector<U>
    return std::vector{x,x+1,x+2};
};
constexpr auto complex_keep_index = [](auto const& x){
    return std::make_pair(complex_comput(x.value()), x.index());
};
constexpr auto distribute = [](auto const& pair){
    return pair.first | transformed([n = pair.second](auto x){
        return std::make_pair(x, n);
    });
};

template<typename T>
void print(T const& w) {
    for (auto const& elem : w) {
        for (auto i : elem) {
            std::cout << i.second << ':' << i.first << ' ';
        }
        std::cout << std::endl;
    }
}

int main() {

    std::vector<int> v{10,20,30};

    using type_of_temp = std::vector<std::pair<std::vector<int>,int>>;

    auto temp = copy_range<type_of_temp>(v | indexed(0)
                                           | transformed(complex_keep_index));
    auto w = temp | transformed(distribute);
    print(w);

    //auto z = v | indexed(0)
    //           | transformed(complex_keep_index)
    //           | transformed(distribute);
    //print(z);
}

実際、defining と using の行のコメントを外すと、zコンパイルはしてもゴミの結果、つまり未定義の動作を生成するコードが得られます。最初の作業範囲に適用する必要があることに注意してくださいcopy_range<type_of_temp>。それ以外の場合、結果のコードは の右側のものと本質的に同じになりますauto z =

なぜそうしなければならないのですか?ワンライナーが機能しない詳細は何ですか?

私はその理由を部分的に理解しており、私の理解/考えを以下にリストしますが、この質問は、このすべての詳細の完全な説明を得るために求めています.

  • 私が観察した未定義の動作はz、破壊された一時的なビューを定義する範囲に起因することを理解しています。
  • コードの動作バージョンを考えると、一時的であることは明らかですv | indexed(0) | transformed(complex_keep_index)
  • ただし、v | indexed(0)それ自体は一時的なものではなく、に供給されtransformed(complex_keep_index)ますか?
  • おそらく 1 つの重要な詳細は、式v | indexed(0)が何も評価しない遅延範囲に過ぎず、範囲を反復するときに計算が行われるように設定するだけであるということです。結局のところ、私は簡単に行うことができますv | indexed(0) | indexed(0) | indexed(0)。これは明確に定義されています。
  • また、全体v | indexed(0) | transformed(complex_keep_index)が明確に定義されています。そうしないと、上記のコードの使用wがおそらく誤動作する可能性があります (UB は、結果が何かが間違っていることを示す必要があることを意味するものではないことを知っています。この時点では、このハードウェアでは問題なく見える可能性があります。明日は休み)。
  • したがって、右辺値をtransformed(distribute);に渡すことには、本質的に間違ったことがあります。
  • しかし、そうすることが間違っているのはdistributeではなく にあります。transformedたとえば、 への変更は明確に定義されているようdistributeに見えるからです。[](auto x){ return x; }
  • では、何が問題なのdistributeですか? これがコードです
constexpr auto distribute = [](auto const& pair){
    return pair.first | transformed([n = pair.second](auto x){
        return std::make_pair(x, n);
    });
};
  • それの何が問題なのですか?返された範囲( this の出力)には、返されたときに範囲外になるtransformedイテレータ/ポインタ/参照がいくつか保持されますが、呼び出し元の何かへの参照であり、生き続けますよね?pair.firstdistributepair
  • ただし、const参照 (例: pair) が一時的なもの (例: の要素) を存続させることができるとしてv | indexed(0) | transformed(complex_keep_index)も、他の何かによって参照されているという理由だけで、その参照が範囲外になったときに一時的なものが存続することを意味するわけではないことを私は知っています。 (の出力の参照/ポインター/イテレーターtransformed([n = …](…){ … })) は範囲外になりません。

おそらく答えは私が上に書いたことにすでにあると思います/願っていますが、それをすべて合理化して完全に理解できるようにするための助けが必要です。

4

0 に答える 0