4

I am currently reading about the range library. My question is about resource ownerships when using views, and how to use these safely.

Let's consider the following code, which print the even year in reverse order:

#include <algorithm>
#include <iostream>

auto print = [](auto elem) -> void {    std::cout << "  " << elem <<"," << std::endl; };
auto isEven = [](size_t year) -> bool {return !(year % 2);};

int main() {

    auto years = std::vector<size_t>{
        1998,        2003,        2011,        2014,
        2017,        2020,        2023};  

    auto view = years | 
        std::ranges::views::filter(isEven) | 
        std::ranges::views::reverse ;

    std::ranges::for_each(view, print);
}

Now, consider the following refactoring, where the view is created with a function:

#...

auto createViewOnEvenYears() {
    auto years = std::vector<size_t>{
        1998,        2003,        2011,        2014,
        2017,        2020,        2023};  

    auto view = years | 
        std::ranges::views::filter(isEven) | 
        std::ranges::views::reverse ;

    return view;
}

int main() {
    auto view = createViewOnEvenYears();
    std::cout << "Printing after vector destruction: " << std::endl;
    std::ranges::for_each(view, print);
}

As the vector has been destroyed when iterating on the view, this lead to a SEGFAULT.

My questions are:

  • are view compatible with the Resource Acquisition Is Initialization principle?
  • Should my function return the range adaptor to prevent this (as proposed below)?
  • More generally, what design pattern(s) should I use to avoid dangling views?
std::vector<size_t> createYears() {
    return std::vector<size_t>{
        1998,        2003,        2011,        2014,
        2017,        2020,        2023};  
}

auto createEvenReverseRangeAdaptor() {
    return 
        std::ranges::views::filter(isEven) | 
        std::ranges::views::reverse ;
}

int main() {
    auto years = createYears();
    std::ranges::for_each(years | createEvenReverseRangeAdaptor(), print);
}
GabrielGodefroy
  • 198
  • 1
  • 8

1 Answers1

1

The simplest solution is to std::move the vector into the pipeline:

auto view = std::move(years) | 
    std::ranges::views::filter(isEven) | 
    std::ranges::views::reverse ;

See it on coliru

There's an implicit std::ranges::views::all applied1 to the leftmost argument of the pipeline, which treats lvalue and rvalue non-view ranges differently. It will wrap an lvalue in ref_view and an rvalue in owning_view.

1 From the deduction guide

template< class R, class Pred >
filter_view( R&&, Pred ) -> filter_view<views::all_t<R>, Pred>;
Caleth
  • 52,200
  • 2
  • 44
  • 75
  • I'm using C++17 (clang) and Eric Niebler's range-v3 library. Appears `std::move(years)` doesn't work for range-v3. Is there an equivalent operation for range-v3? – Eljay Nov 24 '22 at 18:40
  • @Eljay You need to implement something like owning_view if it isn't there – Caleth Nov 24 '22 at 23:39
  • I don't think it's in range-v3. Drat. Thanks for the `owning_view` tip, that'll give me enough to work with! – Eljay Nov 25 '22 at 00:45