Я пишу итератор для пользовательской коллекции:
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];
};
Это должен быть прямой итератор, но когда я проверяю это с помощью статического утверждения, я могу использовать только экземпляры List<T>
, где тип T
является целочисленным типом. Ниже приведен пример нецелых типов float
и небольшого Test
класса. Я также протестировал итератор std::vector<Test>
по умолчанию с идентичным статическим утверждением и не получил ошибки.
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;
}
Я использую стандарт ISO C++20 с компилятором Visual C++. Расширение ошибок дает мне эту трассировку стека ошибок, заканчивающуюся ошибкой «ограничение не было удовлетворено». Ошибка ссылается на эту строку в __msvc_iter_core.hpp:
template <class _Ty>
concept _Integer_like = _Is_nonbool_integral<remove_cv_t<_Ty>> || _Integer_class<_Ty>;
Я не знаю, зачем нужен целочисленный тип. Я не думаю, что класс Test
является проблемой, поскольку утверждение работает с векторным итератором, но я не вижу в классе Iterator ничего, что требовало бы, чтобы T было целочисленным. Почему это требование проверяется?
using difference_type = T;
, вероятно, должно быть что-то вроде using difference_type = std::ptrdiff_t;
. difference_type
— это тип, используемый для представления расстояния между двумя итераторами, поэтому нет смысла использовать его как нецелый тип.
Посмотрите на свой operator<=>(T& other)
Кажется, это не тот код, который вы компилируете. Также проверьте свой end()
.
Частью этого является также упражнение по чтению сообщений об ошибках. В трассировке ошибок перейдите к строке 410 из __msvc_iter_core.hpp
: requires _Signed_integer_like<iter_difference_t<_Ty>>;
Тип вашей разницы не является целым числом со знаком.
Спасибо Майлзу Буднеку и Рэймонду Чену за обнаружение проблемы в комментариях к вопросу.
Разностный тип итератора должен быть целочисленным типом, и поскольку мой код имеет разностный тип T, любое нецелое T вызывает ошибку.
Пишу это как ответ, чтобы можно было отметить вопрос как отвеченный.
Просто будьте благодарны, что вы тестировали нецелочисленный тип T. Если бы вы тестировали только целочисленные типы, компилятор не обнаружил бы его, но это все равно была бы ошибка, хотя ее труднее обнаружить (скажем, если T
был меньшим типом, чем std::ptrdiff_t
, и в этом случае вы бы молча получили неопределенное поведение из-за недостаточного или переполнения, если бы у вас был контейнер, достаточно большой, чтобы превысить то, что может представлять T
).
Проблема здесь в том,
using difference_type = T;
... что почти наверняка неверно.
Помните, что difference_type
представляет собой разницу между двумя итераторами, и это обычно не зависит от того, что они повторяют.
Обратите внимание, что каждая концепция итератора (например, std::forward_iterator<I> по крайней мере требует std::weakly_incrementable<I>, которая имеет вложенное требование
typename iter_difference_t<I>; requires is-signed-integer-like<iter_difference_t<I>>;
Наличие любого псевдонима difference_type
вообще удовлетворило бы первую часть, однако вам нужен тип , похожий на целое число со знаком, поскольку разница удовлетворяет второй части.
Это не обязательно должен быть целочисленный тип; это также может быть что-то очень похожее (например, тип std::_Signed128
MSVC для std::ranges::iota_view
).
Именно поэтому стандартная библиотека тестирует std::_Integer_like
, а не что-то вроде std::integral
.
Самый простой способ решить вашу проблему — это
using difference_type = std::ptrdiff_t;
Если вам не нужны очень большие различия, которые невозможно представить с помощью std::ptrdiff_t
, вам следует использовать этот тип в качестве разницы.
Когда я получу WTF? Диагностика ошибок, я бросаю код на другой компилятор или два, чтобы посмотреть, смогу ли я лучше понять описание проблемы другим компилятором. Я пытался сделать это с вашим кодом, но это привело к слишком большому количеству побочных проблем, чтобы я мог захотеть продолжить. В любом случае, если вы сможете исправить то, что я сделал, то, что говорят clang и GCC, все равно может вам пригодиться.