Reputation: 30717
I naively expected this program to compile and run with a success status:
#include <iterator>
#include <string>
int main()
{
const std::string s = "foo";
auto forward_iter = s.begin();
auto reverse_iter = std::make_reverse_iterator(forward_iter);
auto third_iter = std::make_reverse_iterator(reverse_iter);
return forward_iter != third_iter;
}
It fails to compile, because the type of third_iter
isn't the same as that of the forward_iter
we started with; instead it's a reverse_iterator<reverse_iterator<normal_iterator>>
:
0.cpp:10:25: error: no match for ‘operator!=’ (operand types are ‘__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> >’ and ‘std::reverse_iterator<std::reverse_iterator<__gnu_cxx::__normal_iterator<const char*, std::__cxx11::basic_string<char> > > >’)
return forward_iter != third_iter;
~~~~~~~~~~~~~^~~~~~~~~~~~~
Re-reading the documentation, it appears that std::make_reverse_iterator(it)
is specified to always wrap it
, even if it
is already a reverse iterator (and to some extent that makes sense, because we'll be expecting to use reverse_iterator
members (i.e. base()
).
Is there a standard way to swap between normal (forward) and wrapped (reverse) iterators, without knowing which type I have? Or do I need to write a SFINAE pair of functions to return std::make_reverse_iterator(it)
and it.base()
appropriately?
Upvotes: 2
Views: 724
Reputation: 30717
I managed to make a pair of small functions to swap between forward and reverse iterators (using simple overloading rather than SFINAE):
template<typename Iter>
auto toggle_iterator_direction(Iter it) {
return std::make_reverse_iterator(it);
}
template<typename Iter>
auto toggle_iterator_direction(std::reverse_iterator<Iter> it) {
return it.base();
}
And an alternative version, using a helper type template, modelled on an answer to Determine if a (c++) iterator is reverse:
template<typename Iter>
struct is_reverse_iterator : std::false_type {};
template<typename Iter>
struct is_reverse_iterator<std::reverse_iterator<Iter>> : std::true_type {};
template<typename Iter>
auto toggle_iterator_direction(Iter it) {
if constexpr (is_reverse_iterator<Iter>())
return it.base();
else
return std::make_reverse_iterator(it);
}
Both of these fix the modified test program:
#include <iterator>
#include <string>
int main()
{
const std::string s = "foo";
auto forward_iter = s.begin();
auto reverse_iter = toggle_iterator_direction(forward_iter);
auto third_iter = toggle_iterator_direction(reverse_iter);
return forward_iter != third_iter;
}
Upvotes: 2
Reputation: 238311
Should reversing a reverse_iterator give a forward iterator of the original type?
No. Or at least, that's not how make_reverse_iterator
has been specified. The return type is specified to be reverse_iterator<Iterator>
in the standard.
Is there a standard way to swap between normal (forward) and wrapped (reverse) iterators, without knowing which type I have?
No. Not as far as I know.
Or do I need to write a SFINAE pair of functions to return
std::make_reverse_iterator(it)
andit.base()
appropriately?
You can write it. I can't say whether you need it.
Here's one implementation:
#include <iterator>
#include <type_traits>
// https://stackoverflow.com/a/35408829/2079303
template<typename I>
struct is_reverse_iterator : std::false_type {};
template<typename I>
struct is_reverse_iterator<std::reverse_iterator<I>>
: std::integral_constant<bool, !is_reverse_iterator<I>::value> {};
template<class It>
auto
reverse_or_base(It&& i)
{
if constexpr (is_reverse_iterator<std::decay_t<It>>())
return i.base();
else
return std::make_reverse_iterator(std::forward<It>(i));
}
And a test:
#include <vector>
#include <cassert>
int main() {
std::vector<int> v;
static_assert(
std::is_same_v<
decltype(reverse_or_base(v.rbegin())),
std::vector<int>::iterator
>
);
assert(v.end() == reverse_or_base(v.rbegin()));
static_assert(
std::is_same_v<
decltype(reverse_or_base(v.begin())),
std::vector<int>::reverse_iterator
>
);
assert(v.rend() == reverse_or_base(v.begin()));
}
Upvotes: 2