3

TL;博士

下面最后一行注释的行有什么问题?

// 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
};

所以如果我申请complex_computto 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. 具体来说,我执行以下操作:

  1. 用于boost::adaptors::indexed将索引附加到v
  2. 将每个结果“对”转换为std::pair存储应用程序的index结果和结果,complex_computvalue
  3. 最后将每个转换std::pair<st::vector<int>,int>std::vector<std::pair<int,int>>.

但是,我不得不放弃 2 到 3 之间的范围,std::vector在两个转换之间使用辅助函数“true”。

#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);
}

实际上,对定义和使用的行进行注释可以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);
    });
};
  • 它有什么问题?返回的范围( this 的输出transformed)将包含一些迭代器/指针/引用,这些迭代器/指针/引用在返回pair.first时超出了范围distribute,但pair它是对调用者中某些东西的引用,它一直存在,对吧?
  • 但是我知道,即使const引用(例如pair)可以使临时(例如 的元素v | indexed(0) | transformed(complex_keep_index))保持活动状态,但这并不意味着当该引用超出范围时临时保持活动状态只是因为它又被其他东西引用(输出中的引用/指针/迭代器transformed([n = …](…){ … }))不会超出范围。

我认为/希望答案可能已经在我上面写的内容中,但是我需要一些帮助来简化所有这些,以便我可以一劳永逸地理解它。

4

0 回答 0