Search code examples
c++c++20std-ranges

A class that can be used as a transform view


Assume that I have a class that does some work, and eventually constructs a view over some containers and offers that for iteration. Simplified example:

class BuildAView
{
     BuildAView(std::array<float, 5> &in)
        : mIn{in}
    {
        // Do something non-trivial, so that I need a class instead of just a function
        // getView
    }

    auto getView()
    {
       return mIn | std::views::transform([](float x){return doSomething(x);});
    }
    
private:
     std::array<float, 5> &mIn;

};

This works, but the usage is for (auto && x : buildAView.getView()).

The syntax I'd like is for (auto && x : buildAView), i.e. the class itself acts as a range. However, the trivial attempt doesn't work:

class BuildAView
{
    ...
    // Everything else as above, but instead of getView, directly begin() and end()
    auto begin()
    {
       // Does not work, returns a temporary that ends up dangling...
       return (mIn | std::views::transform([](float x){return doSomething(x);})).begin();
    }
    auto end()
    {
       // Does not work, returns a temporary that ends up dangling...
       return (mIn | std::views::transform([](float x){return doSomething(x);})).end();
    }
    ...
};

but that returns dangling temporaries, as GCC kindly informs me. In addition, the begin and end pointers are of a different type, because the lambda's are formally different type, although this I can fix by replacing the lambda with an appropriate free function.

What I've tried:

  • building the view in the constructor so I can store it. I can make that work sometimes, but the problem is that coughing up the type of the view for the member variable ends up being very convoluted, sometimes impossible (it's easy to accidentally use unnamed or unnameable types), and as far as I know can't be deduced
  • using free begin() and end() does not seem to help, the dangling happens just the same, since there's still no obvious place to store the view

The question: can I build a type that acts like a range, but actually use std::views::transform etc to build said rangem like in the example? Or maybe I'm thinking of this completely wrong, and there's some trivial way to get the same end result?


Solution

  • Instead of having a class member, you can make the range a static variable of the begin function implement the end function in terms of begin:

    auto begin() {
        static auto res = getView();
        return res.begin();
    }
    auto end() {
        return std::next(begin(), mIn.size());
    }
    

    note however, that this leads to inefficient code, when your range is not a random access range and does not work, if the range of getView has a different length than mIn.