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:
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?
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
.