Компилятор требует, чтобы пользовательский итератор имел целочисленный тип, чтобы он считался прямым итератором

Я пишу итератор для пользовательской коллекции:

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 было целочисленным. Почему это требование проверяется?

Когда я получу WTF? Диагностика ошибок, я бросаю код на другой компилятор или два, чтобы посмотреть, смогу ли я лучше понять описание проблемы другим компилятором. Я пытался сделать это с вашим кодом, но это привело к слишком большому количеству побочных проблем, чтобы я мог захотеть продолжить. В любом случае, если вы сможете исправить то, что я сделал, то, что говорят clang и GCC, все равно может вам пригодиться.

user4581301 03.05.2024 06:14
using difference_type = T;, вероятно, должно быть что-то вроде using difference_type = std::ptrdiff_t;. difference_type — это тип, используемый для представления расстояния между двумя итераторами, поэтому нет смысла использовать его как нецелый тип.
Miles Budnek 03.05.2024 06:23

Посмотрите на свой operator<=>(T& other) Кажется, это не тот код, который вы компилируете. Также проверьте свой end() .

Avi Berger 03.05.2024 06:36

Частью этого является также упражнение по чтению сообщений об ошибках. В трассировке ошибок перейдите к строке 410 из __msvc_iter_core.hpp: requires _Signed_integer_like<iter_difference_t<_Ty>>; Тип вашей разницы не является целым числом со знаком.

Raymond Chen 03.05.2024 06:42
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
2
4
96
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Спасибо Майлзу Буднеку и Рэймонду Чену за обнаружение проблемы в комментариях к вопросу.

Разностный тип итератора должен быть целочисленным типом, и поскольку мой код имеет разностный тип T, любое нецелое T вызывает ошибку.

Пишу это как ответ, чтобы можно было отметить вопрос как отвеченный.

Просто будьте благодарны, что вы тестировали нецелочисленный тип T. Если бы вы тестировали только целочисленные типы, компилятор не обнаружил бы его, но это все равно была бы ошибка, хотя ее труднее обнаружить (скажем, если T был меньшим типом, чем std::ptrdiff_t, и в этом случае вы бы молча получили неопределенное поведение из-за недостаточного или переполнения, если бы у вас был контейнер, достаточно большой, чтобы превысить то, что может представлять T).

Peter 03.05.2024 08:39
Ответ принят как подходящий

Проблема здесь в том,

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, вам следует использовать этот тип в качестве разницы.

Другие вопросы по теме