I am making a circular iterator class that behaves like a forward iterator, except it loops back to the beginning after reaching the end of a range.
template <typename T>
struct CircularIterator
{
CircularIterator(T* begin, T* end);
// Omitted overloaded operators
T* mBegin; // Points to beginning of range
T* mIter; // Current item
T* mEnd; // Points to end of range
};
There is no conversion from an STL iterator (e.g., std::vector<int>::iterator>
) to a raw pointer (T*
). The following code compiles with an error:
std::vector<int> vec{1, 2, 3};
CircularIterator<int> iter(vec.begin(), vec.end());
error: cannot convert ‘__gnu_cxx::__normal_iterator<int*, std::vector<int> >’ to ‘int*’ in initialization
How do I create a template class that can accept any type that satisfies std::forward_iterator<T>
in the constructor? I would like to avoid creating a new template instance for each iterator type that is used (e.g., new CircularIterator
for std::array<T>::iterator
and std::deque<T>::iterator
.)
Any advice would be appriciated. I'm definately at the far end of my template/concept knowledge and look forward to any resources to learn more. Thank you.
CodePudding user response:
You can constrain the template parameter I
of CircularIterator
must satisfy std::forward_iterator
, e.g.
#include <iterator>
template <std::forward_iterator I>
struct CircularIterator
{
CircularIterator() = default;
CircularIterator(I begin, I end);
// Omitted overloaded operators
I mBegin; // Points to beginning of range
I mIter; // Current item
I mEnd; // Points to end of range
};
Note that you need to provide a default constructor for CircularIterator
because std::forward_iterator
requires a default constructor.
CodePudding user response:
On possible way using SFINAE with is_base_of_v
and forward_iterator_tag
:
#include <iterator>
#include <type_traits>
template <class It, class EndIt,
std::enable_if_t<std::is_base_of_v<
std::forward_iterator_tag,
typename std::iterator_traits<It>::iterator_category>,int> = 0>
struct CircularIterator {
CircularIterator(It begin, EndIt end)
: mBegin(begin), mIter(begin), mEnd(end) {}
It mBegin; // Points to beginning of range
It mIter; // Current item
EndIt mEnd; // Points to end of range
};
A C 20 option with possibly fewer instantiations requiring the iterator to be a contiguous_iterator
instead:
#include <concepts>
#include <iterator>
#include <type_traits>
template <class T>
struct CircularIterator {
CircularIterator(std::contiguous_iterator auto begin,
std::contiguous_iterator auto end)
: mBegin(begin != end ? &*begin : nullptr),
mIter(mBegin),
mEnd(std::next(mBegin, std::distance(begin, end))) {}
T* mBegin; // Points to beginning of range
T* mIter; // Current item
T* mEnd; // Points to end of range
};
... and used like in your example:
#include <vector>
int main() {
std::vector<int> vec{1, 2, 3};
CircularIterator<int> iter(vec.begin(), vec.end());
}