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_comput
とv
、次のようになります。
{{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の範囲を利用しようとしました. 具体的には、次のことを行います。
boost::adaptors::indexed
の各要素にインデックスを付けるために使用しますv
- 得られた各「ペア」を に変換し、を に適用した結果を に
std::pair
格納します。index
complex_comput
value
- そして最後にそれぞれを に変換
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.first
distribute
pair
- ただし、
const
参照 (例:pair
) が一時的なもの (例: の要素) を存続させることができるとしてv | indexed(0) | transformed(complex_keep_index)
も、他の何かによって参照されているという理由だけで、その参照が範囲外になったときに一時的なものが存続することを意味するわけではないことを私は知っています。 (の出力の参照/ポインター/イテレーターtransformed([n = …](…){ … })
) は範囲外になりません。
おそらく答えは私が上に書いたことにすでにあると思います/願っていますが、それをすべて合理化して完全に理解できるようにするための助けが必要です。