I'm writing an iterator for a custom collection:
template <typename T>
class List
{
public:
class Iterator
{
public:
using difference_type = T;
using value_type = T;
using pointer = const T*;
using reference = const T&;
using iterator_category = std::forward_iterator_tag;
Iterator(T* ptr = nullptr) : _ptr(ptr) {}
Iterator& operator++() { _ptr++; return *this; }
Iterator operator++(int) { Iterator retval = *this; ++(*this); return retval; }
std::strong_ordering operator<=>(Test& other) { return _x <=> other._x; }
bool operator==(Iterator other) const { return _ptr == other._ptr; }
bool operator!=(Iterator other) const { return !(_ptr == other._ptr); }
reference operator*() const { return *_ptr; }
pointer operator->() const { return _ptr; }
reference operator=(const T& value) { *_ptr = value; return value; }
private:
T* _ptr;
};
Iterator begin() { return Iterator(_data); }
Iterator end() { return Iterator(_data + 4); }
List() // Generate generic data
{
for (int i = 0; i < 5; i++)
{
_data[i] = i + 1;
}
}
private:
T _data[5];
};
This needs to be a forward iterator, but when I check this with a static assert, I can only use List<T>
instances where the type T
is an integral type. Below is an example of the non-integral types float
and a small Test
class. I also tested the default std::vector<Test>
iterator with an identical static assert and didn't get an error.
class Test
{
public:
Test(int x = 0) : _x(x) {}
void Increment() { _x++; }
std::strong_ordering operator<=>(Test& other) { return _x <=> other._x; }
int operator=(int x) { _x = x; return x; }
friend std::ostream& operator<<(std::ostream& os, const Test& m) { return os << m._x; };
private:
int _x;
};
int main()
{
List<int> container1;
List<Test> container2;
List<float> container3;
std::vector<Test> container4;
static_assert(std::forward_iterator<decltype(container1.begin())>); // no error
static_assert(std::forward_iterator<decltype(container2.begin())>); // ERROR
static_assert(std::forward_iterator<decltype(container3.begin())>); // ERROR
static_assert(std::forward_iterator<decltype(container4.begin())>); // no error
// This loop works correctly if the static asserts are commented out
for (List<int>::Iterator it = container1.begin(); it != container1.end(); it++)
{
std::cout << *it << "\n";
}
return 0;
}
I'm using the C++20 ISO standard with the Visual C++ compiler. Expanding the errors gets me this error stack trace ending with the error "the constraint was not satisfied". The error references this line in __msvc_iter_core.hpp:
template <class _Ty>
concept _Integer_like = _Is_nonbool_integral<remove_cv_t<_Ty>> || _Integer_class<_Ty>;
I don't know why an integral type would be required. I don't think the Test
class is the problem since the assert works with a vector iterator, but I don't see anything in the Iterator class that would require T to be integral. Why is this requirement being tested?
The issue here is
using difference_type = T;
... which is almost certainly wrong.
Remember that the difference_type
represents the difference between two iterators, and this usually doesn't depend on what they're iterating over.
Note that every iterator concept (such as std::forward_iterator<I>
at the very least requires std::weakly_incrementable<I>
, which has the nested requirement
typename iter_difference_t<I>; requires is-signed-integer-like<iter_difference_t<I>>;
Having any difference_type
alias at all would satisfy the first part, however, you need a signed-integer-like type as the difference the satisfy the second part.
This doesn't have to be an integral type; it can also be something very similar (e.g. MSVC's std::_Signed128
type for std::ranges::iota_view
).
This is also why the standard library is testing for std::_Integer_like
, not something like std::integral
.
The most easy way to fix your problem would be
using difference_type = std::ptrdiff_t;
Unless you need extremely large differences which couldn't be represented by std::ptrdiff_t
, you should use that type as a difference.