Как я могу стать владельцем коллекции с помощью range-v3?

Я хочу вернуть диапазон из функции, представляющей представление коллекции STL, примерно так:

auto createRange() {
    std::unordered_set<int> is = {1, 2, 3, 4, 5, 6};

    return is | view::transform([](auto&& i) {
        return i;
    });
}

Однако view::transform не становится владельцем is, поэтому, когда я запускаю это, возникает неопределенное поведение, потому что is освобождается при выходе createRange.

int main(int argc, char* argv[]) {
    auto rng = createRange();
    ranges::for_each(rng, [](auto&& i) {
        std::cout << std::to_string(i) << std::endl;
    });
}

Если я попробую std::move(is) в качестве входных данных, я получу статическое утверждение, указывающее, что я не могу использовать ссылки rvalue в качестве входных данных для view. Есть ли способ гарантировать, что представление станет владельцем коллекции?

Изменить: дополнительная информация

Хочу добавить уточняющую информацию. У меня есть поток данных data, представление которого преобразует данные в структуру Foo, которая выглядит примерно так:

struct Foo {
    std::string name;
    std::unordered_set<int> values;
}

// Take the input stream and turn it into a range of Foos
auto foos = data | asFoo();

Что я хочу сделать, так это создать диапазон std::pair<std::string, int>, распределив имя по значениям. Моя наивная попытка выглядит примерно так:

auto result = data | asFoo() | view::transform([](auto&& foo) {
    const auto& name = foo.name;
    const auto& values = foo.values;
    return values | view::transform([name](auto&& value) {
        return std::make_pair(name, value);
    }
}) | view::join;

Однако это приводит к неопределенному поведению, поскольку values освобождается. Единственный способ обойти это — сделать values std::shared_ptr и зафиксировать его в лямбда-выражении, переданном view::transform, чтобы сохранить его время жизни. Это кажется неэлегантным решением.

Я думаю, что я ищу представление, которое будет владеть исходной коллекцией, но не похоже, что в range-v3 это есть.

В качестве альтернативы я мог бы просто создать распределенную версию, используя старый добрый цикл for, но, похоже, он не работает с view::join:

auto result = data | asFoo() | view::transform([](auto&& foo) {
    const auto& name = foo.name;
    const auto& values = foo.values;

    std::vector<std::pair<std::string, std::string>> distributedValues;
    for (const auto& value : values) {
        distributedValues.emplace_back(name, value);
    }

    return distributedValues;
}) | view::join;

Даже если это работало с view::join, я также думаю, что смешанная метафора диапазонов и циклов также неэлегантна.


person Taylor    schedule 10.05.2018    source источник
comment
Я не понимаю мотивации здесь; почему бы не изменить is, чтобы он содержал преобразованные данные и возвращал их напрямую? Зачем тебе вообще здесь вид?   -  person ildjarn    schedule 15.05.2018
comment
Я упростил пример, чтобы сделать проблему более ясной. На самом деле у меня больший поток просмотров. Часть этого конвейера создает объект с функцией, возвращающей коллекцию. Я хочу получить представление об этой коллекции, а затем объединить представления, чтобы создать более крупное представление. Я работаю с большим набором данных, поэтому не хочу, чтобы все они одновременно хранились в памяти.   -  person Taylor    schedule 16.05.2018


Ответы (1)


Представления не владеют данными, которые они представляют. Если вам нужно обеспечить постоянство данных, то сами данные должны быть сохранены.

auto createRange() {
    //We're using a pointer to ensure that the contents don't get moved around, which might invalidate the view
    std::unique_ptr<std::unordered_set<int>> is_ptr = std::make_unique<std::unordered_set<int>>({1,2,3,4,5,6});
    auto & is = *is_ptr;
    auto view = is | view::transform([](auto&& i) {return i;});
    return std::make_pair(view, std::move(is_ptr));
}

int main() {
    auto[rng, data_ptr] = createRange();
    ranges::for_each(rng, [](auto&& i) {
        std::cout << std::to_string(i) << std::endl;
    });
}

Альтернативный метод — убедиться, что функции предоставлен набор данных, из которого будет создано представление:

auto createRange(std::unordered_set<int> & is) {
    return is | view::transform([](auto&& i) {return i;});
}

int main() {
    std::unordered_set<int> is = {1,2,3,4,5,6};
    auto rng = createRange(is);
    ranges::for_each(rng, [](auto&& i) {
        std::cout << std::to_string(i) << std::endl;
    });
}

Любое решение должно в общих чертах отражать то, что должно делать ваше решение для вашего проекта.

person Xirema    schedule 10.05.2018
comment
Первый — это подход, который я в конечном итоге использовал, но я надеялся, что в range-v3 будет лучший способ сделать это. Для большего контекста у меня есть Rng‹Foo›, где у Foo есть функция, которая возвращает коллекцию. Я хочу преобразовать этот Rng‹Foo› в Rng‹Rng‹Bar››, где Bar основан на коллекции, возвращаемой Foo. Я могу обновить свой вопрос с более подробной информацией, если это поможет. - person Taylor; 10.05.2018
comment
@Taylor Возможно, вы захотите обновить свой вопрос. - person Xirema; 10.05.2018