pnklein
pnklein

Reputation: 985

Partial template specialization based on lval/rval?

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

Answers (1)

Yakk - Adam Nevraumont
Yakk - Adam Nevraumont

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.

live example.

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

Related Questions