Home > OS >  How do I create a template class that can accept any type that satisfies std::forward_iterator<T&
How do I create a template class that can accept any type that satisfies std::forward_iterator<T&

Time:07-19

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());
}

Demo

  • Related