Работает ли boost::combine() с выводом метода?

#c #boost

#c #повышение

Вопрос:

В приведенном ниже тестовом примере я использую boost::combine для итерации вывода функции getPoints() .

Ожидаемый результат

Я ожидаю, что (1, 2, 3) будет напечатано 6 раз; поскольку я эффективно архивирую два списка —

([точка, point, point], [точка, point, point]).

Фактический вывод

Вывод для меня неожиданный и неправильный. Первые две строки отключены, что указывает на повреждение памяти?

 (0, 0, 3)          // <-- wrong!
(52246144, 0, 3)   // <-- wrong! memory corruption?
(1, 2, 3)
(1, 2, 3)
(1, 2, 3)
(1, 2, 3)
  

Это также можно проверить онлайн здесь,http://cpp.sh/622h4.

Это ошибка?

Приведенный ниже код —

 #include <iostream>
#include <vector>

#include <boost/range/combine.hpp>


struct Point {
    int x, y, z;
};

const std::vector<Point> getPoints() {
    // There is only one Point in the entire code, which is (1, 2, 3).
    const Point point = {1, 2, 3};
    // Return a vectore of 3 copies of the point (1, 2, 3).
    return {point, point, point};
}


int main() {
    // Zip over two copies of 3-tuples of {1, 2, 3}.
    for (const autoamp; zipped : boost::combine(getPoints(), getPoints())) {
        auto p1 = zipped.get<0>();
        auto p2 = zipped.get<1>();
        // Expected output is (1, 2, 3), six times.
        std::cout << "(" << p1.x << ", " << p1.y << ", " << p1.z << ")" << std::endl;
        std::cout << "(" << p2.x << ", " << p2.y << ", " << p2.z << ")" << std::endl;
    }

    return 0;
}
  

Ответ №1:

Здесь у вас неопределенное поведение при доступе к висячей ссылке. Это можно исправить с помощью

 const auto points1 = getPoints();
const auto points2 = getPoints();

for (const autoamp; zipped : boost::combine(points1, points2)) {
    // ...
}
  

Ссылки на значения Rvalue всегда проблематичны при работе с библиотеками диапазонов. Очевидно, что алгоритм диапазона, подобный boost::combine , не копирует аргумент. И это создает новый объект прокси-диапазона, который делает невозможным продление срока службы переданного временного диапазона.

Напротив, цикл for на основе диапазона for(const autoamp; item: getPoints()) {...} расширяется до

 {
    auto amp;amp; __range = getPoints();
    for (auto __begin = begin_expr, __end = end_expr; __begin != __end;   __begin) {

        range_declaration = *__begin;
        loop_statement
    }
} 
  

где время жизни getPoints() увеличивается путем привязки его к ссылке rvalue. Представьте шаблон функции combine в виде

 template<class Rng>
auto combine(Rngamp;amp; rng) {
    auto amp;amp; == range; // Nice try but doesn't help

    // ...

    return someProxyRange;
}
  

Этот шаблон функции ничего не может сделать для продления срока службы rng , поскольку он действует в области, отличной от rng , которая поступает со стороны клиента. В цикле for, основанном на диапазоне, это отличается. Область временного (например, getPoints() ) и ссылки пересылки autoamp;amp; __range находятся в одной и той же области, следовательно, время жизни может быть увеличено.

Комментарии:

1. Не могли бы вы уточнить, что такое висячая ссылка? Если бы вы использовали const auto zipped , сработало бы это также?

2. Спасибо! Почему ссылка здесь теряется, если я использую функцию напрямую? Я ожидал, что combine сохранит копию результата внутри — похоже, этого не происходит. Ожидается ли это? (Подтверждение на втором 0 , исправлено.)

3. Например, for(const autoamp; point: getPoints()) {...} должно работать должным образом. Почему это не удается с combine(...) ?

4. @KalEl Посмотрите на range-for и следующее предложение: Если range_expression возвращает временное значение, его время жизни продлевается до конца цикла, как указано привязкой к ссылке пересылки __range, но помните, что время жизни любого временного значения в пределах range_expression не продлевается. Вы создаете временные векторы с помощью getPoints .

5. @rafix07 Это должно быть в ответе / другом ответе.