问题描述
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,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
};
所以如果我将 complex_comput
应用到 v
,我会得到,
{{10,11,12},{20,21,22},{30,31,32}}
如果我也连接结果,我最终会得到这个:
{10,12,22,30,32}
但是,我想跟踪每个数字来自的索引,结果将编码如下:
0 10
0 11
0 12
1 20
1 21
1 22
2 30
2 31
2 32
为了实现这一点,我(最终)想出了这个解决方案,我试图利用 Boost.range 的范围。具体来说,我执行以下操作:
- 使用
boost::adaptors::indexed
将索引附加到v
的每个元素 - 将
std::pair
中的每个结果“对”转换为存储index
和应用complex_comput
到value
的结果, - 最后转换
std::pair<st::vector<int>,int>
中的每个std::vector<std::pair<int,int>>
。
但是,我不得不放弃 2 和 3 之间的范围,在两个转换之间使用辅助“true”std::vector
。
#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+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,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);
}
确实,对定义和使用 z
的行进行注释会为您提供编译但生成垃圾结果的代码,即未定义的行为。请注意,将 copy_range<type_of_temp>
应用于第一个工作范围是必要的,否则生成的代码与 auto z =
右侧的代码基本相同。
我为什么要这样做?是什么细节导致 oneliner 无法正常工作?
我部分理解原因,我将在下面列出我的理解/想法,但我问这个问题是为了得到对所有细节的彻底解释。
- 我知道我观察到的未定义行为源于
z
是一个范围,它定义了一些已被销毁的临时视图; - 鉴于代码的工作版本,很明显临时是
v | indexed(0) | transformed(complex_keep_index)
; - 然而,
v | indexed(0)
本身不是被馈送到transformed(complex_keep_index)
的临时对象吗? - 可能一个重要的细节是表达式
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);
});
};
- 它有什么问题?返回的范围(此
transformed
的输出)将保存一些指向pair.first
的迭代器/指针/引用,这是distribute
返回时超出范围的一部分,但pair
是对调用者中某些内容的引用,它继续存在,对吗? - 但是我知道即使
const
引用(例如pair
)可以保持临时(例如v | indexed(0) | transformed(complex_keep_index)
的元素)活着,但这并不意味着临时保持当该引用超出范围时仍然活着,只是因为它又被其他没有超出范围的东西(transformed([n = …](…){ … })
的输出中的引用/指针/迭代器)引用。
我认为/希望答案可能已经在我上面写的内容中,但是我需要一些帮助来简化所有这些,以便我能够一劳永逸地理解它。
解决方法
暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!
如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。
小编邮箱:dio#foxmail.com (将#修改为@)