This solution does not introduce unnecessary copies and does not exhibit incorrect forwarding as suggested by some comments. Explanation below.
You can use some wrapper which has begin and end functions that actually
return reverse iterators.
template<class T>
struct revert_wrapper
{
T o;
revert_wrapper(T&& i) : o(std::forward<T>(i)) {}
};
template<class T>
auto begin(revert_wrapper<T>& r)
{
using std::end;
return std::make_reverse_iterator(end(r.o));
}
template<class T>
auto end(revert_wrapper<T>& r)
{
using std::begin;
return std::make_reverse_iterator(begin(r.o));
}
template<class T>
auto begin(revert_wrapper<T> const& r)
{
using std::end;
return std::make_reverse_iterator(end(r.o));
}
template<class T>
auto end(revert_wrapper<T> const& r)
{
using std::begin;
return std::make_reverse_iterator(begin(r.o));
}
template<class T>
auto reverse(T&& ob)
{
return revert_wrapper<T>{ std::forward<T>(ob) };
}
Used like this:
std::vector<int> v{1, 2, 3, 4};
for (auto i : reverse(v))
{
std::cout << i << "\n";
}
or in your case
for ( auto& i : reverse(lifo_stack) ) {
cout << "Current loop iteration has i = " << i << endl;
cout << "Popped an item from the stack: " << lifo_stack.pop() << endl;
}
Since fowarding is not an easy topic and there is misconception around I'll further explain some details. I'll use std::vector<int>
as an example for the "to be reversed" type T
.
1. The function template reverse
.
1.1 Passing an lvalue std::vector<int>
:
std::vector<int> v{1, 2, 3, 4};
auto&& x = reverse(v);
The compiler created instance of reverse
in this case would look like:
template<>
auto reverse<std::vector<int>&>(std::vector<int>& ob)
{
return revert_wrapper<std::vector<int>&>{ std::forward<std::vector<int>&>(ob) };
}
We see two things here:
- The
T
of revert_wrapper
will be std::vector<int>&
, so no copy involved.
- we're forwarding an lvalue as an lvalue to the constructor of
revert_wrapper
1.2 Passing an rvalue std::vector<int>
std::vector<int> foo();
auto&& x = reverse(foo());
We look again at the instantiation of the function template:
template<>
auto reverse<std::vector<int>>(std::vector<int>&& ob)
{
return revert_wrapper<std::vector<int>>{ std::forward<std::vector<int>>(ob) };
}
And can again note two things:
- The
T
of revert_wrapper
will be std::vector<int>
, thus copy the vector, preventing the rvalue from going out of scope before any range based loop can run
- an rvalue
std::vector<int>&&
will be forwarded to the constructor of revert_wrapper
2. The class template revert_wrapper
and its constructor
2.1 The revert_wrapper
created by reverse
in case of an lvalue std::vector<int>&
template<>
struct revert_wrapper<std::vector<int>&>
{
std::vector<int>& o;
revert_wrapper(std::vector<int>& i) :
o(std::forward<std::vector<int>&>(i)) {}
};
As noted above: No copies involved as we store a reference.
The forward
also seems familiar and indeed it is just the same as above within reverse
: We forward an lvalue as lvalue reference.
2.2 The revert_wrapper
created by reverse
in case of an rvalue std::vector<int>&&
template<>
struct revert_wrapper<std::vector<int>>
{
std::vector<int> o;
revert_wrapper(std::vector<int>&& i) :
o(std::forward<std::vector<int>>(i)) {}
};
This time we have the object stored by value to prevent a dangling reference.
Also the forwarding is fine: We forwarded the rvalue reference from reverse
to the revert_wrapper
constructor and we forward it on to the std::vector
constructor. We could've used static_cast<T&&>(i)
in the same way but we're not (std::)mov(e)
ing from an lvalue, we're forwarding:
- lvalues as lvalues and
- rvalues as rvalues.
We can also see one more thing here:
The only available constructor of the revert_wrapper
instance that stores by value takes an rvalue. Therefore, we can't (easily) trick this class to make unnecessary copies.
Note that replacing std::forward
with std::move
inside the initializer of o
in the revert_wrapper
constructor would actually be wrong.