Понимание композиции ленивых функций на основе диапазона

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

Или, другими словами, что делает конвейерную передачу четко определенным, а конвейерную передачу — неопределенным поведением?

Расширенная версия

У меня есть контейнер с вещами,

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

поэтому, если бы я применил к , я бы получил,

      {{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_computк value,
  3. и, наконец, преобразование каждого 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+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);
}

Действительно, раскомментирование строк, определяющих и использующих, дает вам код, который компилируется, но генерирует бесполезные результаты, то есть поведение undefined. Обратите внимание, что применение copy_range<type_of_temp>к первому, рабочему, диапазону, иначе результирующий код по сути такой же, как тот, что справа от auto z =.

Почему я должен это делать? Какие детали делают oneliner неработоспособным?

Я частично понимаю причину, и я перечислю свое понимание/мысли ниже, но я задаю этот вопрос, чтобы получить подробное объяснение всех деталей этого.

  • Я понимаю, что неопределенное поведение, которое я наблюдаю, связано с zявляющийся диапазоном, определяющим представление о каком-то временном объекте, который был уничтожен;
  • учитывая рабочую версию кода, очевидно, что временный ;
  • однако сам по себе не является временным, который подается на transformed(complex_keep_index)?
  • Вероятно, одна важная деталь состоит в том, что выражение v | indexed(0)является не более чем ленивым диапазоном, который ничего не оценивает, а просто настраивает все так, что при повторении диапазона выполняются вычисления; в конце концов, я могу легко сделать v | indexed(0) | indexed(0) | indexed(0), который хорошо определен;
  • а также все хорошо определено, в противном случае код выше, использующий wвероятно, будет вести себя неправильно (я знаю, что UB не означает, что результат должен показывать, что что-то не так, и все может выглядеть нормально на этом оборудовании, в этот момент, и сломаться завтра).
  • Таким образом, что-то изначально неправильное - это передача rvalue в transformed(distribute);
  • но что в этом плохого, так это , а не , потому что, например, изменение на [](auto x){ return x; }кажется, хорошо определен.
  • Так что же не так? Вот код
      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возвращает, но является ссылкой на что-то в вызывающей программе, которая продолжает жить, верно?
  • Однако я знаю, что хотя constссылка (например pair) может хранить временную (например, элементы v | indexed(0) | transformed(complex_keep_index)) живым, это не означает, что временное остается живым, когда эта ссылка выходит за пределы области действия только потому, что на нее, в свою очередь, ссылается что-то еще (ссылки/указатели/итераторы в выводе transformed([n = …](…){ … })), что не выходит за рамки.

Я думаю/надеюсь, что, вероятно, ответ уже есть в том, что я написал выше, однако мне нужна помощь, чтобы упорядочить все это, чтобы я мог понять это раз и навсегда.

0 ответов

Другие вопросы по тегам