Reputation: 985
I'm writing an adaptor class to allow me to write this:
for (auto item : ButFirst(myvec)) { ... }
The following class definition works well when, as above, the argument is an lval:
template <typename Container>
class ButFirst {
const Container& container_;
public:
ButFirst(const Container& container) : container_(container) {}
typename Container::const_iterator begin() { return ++(container_.begin()); }
typename Container::const_iterator end() { return container_.end(); }
};
However, I want to also use it when the argument is an rval, as in:
for (auto item : ButFirst(get_vec(3)) { ... }
I would therefore write the following class definition:
template <typename Container>
class ButFirst {
const Container container_;
public:
ButFirst(Container&& container) : container_(std::move(container)) {}
...
};
How can I write one class definition that handles both? Or maybe one class and some partial template specializations?
Upvotes: 1
Views: 92
Reputation: 275220
template <typename Container>
class ButFirst {
Container container_;
public:
ButFirst(Container container) : container_(std::forward<Container>(container)) {}
typename std::decay_t<Container>::const_iterator begin() { return std::next(container_.begin()); }
typename std::decay_t<Container>::const_iterator end() { return container_.end(); }
};
then add deduction guide:
template <typename Container>
ButFirst(Container&&)->ButFirst<Container>;
and ... poof.
Despite appearances this only copies on rvalues.
The forwarding reference in the ButFirst
deduction guide deduces Container
to be a reference type when you construct it with an lvalue, and Container
as a value type when you pass it an rvalue. This is exactly what you want.
And that isn't a coincidence; forwarding references work that way for a reason.
As an aside, my version of this is a bit different.
I define a range_view
type that can be constructed from a range-like, and stores 2 iterators.
range_view
methods like begin()
are const
; range_view
is like a pointer, not like a value.
It has methods:
range_view except_front(std::size_t n=1)const;
range_view except_back(std::size_t n=1)const;
range_view only_front(std::size_t n=1)const;
range_view only_back(std::size_t n=1)const;
which clamp n
based on how big the range is.
Then:
for(auto item: range_view(container).except_front())
does what yours does.
I consider checking for iterator counting errors and returning empty range in that case well worth the overhead for the reliability I get from it, as that math happens once per loop, not once per iteration.
Also, range_view
has ridiculously many other useful uses.
Upvotes: 3