проблемы с const, span и итератором - PullRequest
0 голосов
/ 04 мая 2018

Я пытаюсь написать итератор, который перебирает контейнер по индексу. A It и const It позволяют изменять содержимое контейнера. A Const_it и const Const_it запрещают изменять содержимое контейнера.

После этого я пытаюсь написать span<T> поверх контейнера. Для типа T, который не является константным, как const span<T>, так и span<T> позволяют изменять содержимое контейнера. И const span<const T>, и span<const T> запрещают изменять содержимое контейнера.

Код не компилируется, потому что:

    // *this is const within a const method
    // But It<self_type> requires a non-const *this here.
    // So the code does not compile
    It<self_type> begin() const { return It<self_type>(*this, 0); }

Если я заставлю конструктор It принять контейнер const, он будет выглядеть неправильно, потому что итератор может изменить содержимое контейнера.

Если я избавлюсь от const метода, то для неконстантного типа T a const span<T> не сможет изменить контейнер.

It наследуется от Const_it, чтобы разрешить неявное преобразование из It в Const_it во время создания шаблона.

Я использую указатель вместо ссылки в итераторах (const C* container_;), чтобы разрешить назначать один итератор другому.

Я подозреваю, что здесь что-то не так, потому что я даже думаю о:

Отбрасывает ли const от * это вызывающее неопределенное поведение?

Но я не знаю, как это исправить.

Тест:

#include <vector>
#include <numeric>
#include <iostream>

template<typename C>
class Const_it {
    typedef Const_it<C> self_type;
public:
    Const_it(const C& container, const int ix)
            : container_(&container), ix_(ix) {}
    self_type& operator++() {
        ++ix_;
        return *this;
    }

    const int& operator*() const {
        return ref_a()[ix_];
    }

    bool operator!=(const self_type& rhs) const {
        return ix_ != rhs.ix_;
    }

protected:
    const C& ref_a() const { return *container_; }
    const C* container_;
    int ix_;
};

template<typename C>
class It : public Const_it<C> {
    typedef Const_it<C> Base;
    typedef It<C> self_type;
public:
    //It(const C& container.
    It(C& container, const int ix)
            : Base::Const_it(container, ix) {}
    self_type& operator++() {
        ++ix_;
        return *this;
    }

    int& operator*() const {
        return mutable_a()[ix_];
    }

private:
    C& mutable_a() const { return const_cast<C&>(ref_a()); }
    using Base::ref_a;
    using Base::container_;
    using Base::ix_;
};


template <typename V>
class span {
    typedef span<V> self_type;
public:
    explicit span(V& v) : v_(v) {}
    It<self_type> begin() { return It<self_type>(*this, 0); }
    // *this is const within a const method
    // But It<self_type> requires a non-const *this here.
    // So the code does not compile
    It<self_type> begin() const { return It<self_type>(*this, 0); }
    It<self_type> end() { return It<self_type>(*this, v_.size()); }
    It<self_type> end() const { return It<self_type>(*this, v_.size()); }

    int& operator[](const int ix) {return v_[ix];}
    const int& operator[](const int ix) const {return v_[ix];}
private:
    V& v_;
};


int main() {
    typedef std::vector<int> V;
    V v(10);
    std::iota(v.begin(), v.end(), 0);
    std::cout << v.size() << "\n";
    const span<V> s(v);
    for (auto&& x : s) {
        x = 4;
        std::cout << x << "\n";
    }
}

Ответы [ 2 ]

0 голосов
/ 04 мая 2018

Изучив решение Acorn, я нашел другое исправление.

Это позволяет использовать один и тот же шаблон итератора для std::vector<T> и span.

Два случая разные. Для неконстантного типа T, const std::vector<T> запрещает изменять свой элемент. const span<T> позволяет менять свои элементы.

Основное отличие - It<const self_type>(*this, 0); в классе span и S& span_; вместо const S& span_; в классе It.

Исправлено:

#include <vector>
#include <iostream>

template<typename S>
class It {
    typedef It<S> self_type;
public:
    It(S& span, const int ix)
            : span_(span), ix_(ix) {}

    self_type& operator++() {
        ++ix_;
        return *this;
    }

    int& operator*() const {
        return span_[ix_];
    }

    bool operator!=(const self_type& rhs) const {
        return &span_ != &rhs.span_ or ix_ != rhs.ix_;
    }

private:
    S& span_;
    int ix_;
};

template<typename V>
class span {
    typedef span<V> self_type;
public:
    explicit span(V& v) : v_(v) {}

    It<const self_type> begin() const {
        return It<const self_type>(*this, 0);
    }

    It<const self_type> end() const {
        return It<const self_type>(*this, v_.size());
    }

    int& operator[](const int ix) const { return v_[ix]; }

private:
    V& v_;
};

int main() {
    // Test adding iterator to a span
    typedef std::vector<int> V;
    V v(10);
    const span<V> s(v);
    for (auto&& x : s) {
        x = 4;
        std::cout << x << " ";
    }
    std::cout << "\n";
    // Test adding iterator to a std::vector
    const It<V> begin(v, 0);
    const It<V> end(v, v.size());

    for (auto it = begin; it != end; ++it) {
        *it = 10;
        std::cout << *it << " ";
    }

    std::cout << "\n";
}
0 голосов
/ 04 мая 2018

Есть две основные заметки, которые нужно сказать, чтобы сделать эту работу. Во-первых:

Если я заставлю конструктор It принимать контейнер const, он не будет выглядеть правильно, потому что итератор может изменить содержимое контейнера.

Не совсем, потому что C в вашем template<typename C> class It это не фактический контейнер, а span<V>. Другими словами, взгляните на:

It<self_type> begin() const { return It<self_type>(*this, 0); }

Здесь self_type означает const span<V>, поэтому вы возвращаете It<const span<V>>. Таким образом, ваш итератор может делать все, что может быть сделано с const span - но контейнер все еще не - const. Тогда имя переменной container_ не является удачным.

Для типа T, который не const, и const span<T>, и span<T> позволяют изменять содержимое контейнера. И const span<const T>, и span<const T> запрещают изменять содержимое контейнера.

Кроме того, поскольку вы хотите, чтобы const span было разрешено изменять содержимое, то то, что вы должны написать внутри самого span (обратите внимание на const):

int& operator[](const int ix) const {return v_[ix];}
// Removing the other `const` version:
// const int& operator[](const int ix) const {return v_[ix];}

После прояснения этих двух битов вы можете создать рабочий пример. Вот пример, основанный на вашем коде и упрощенный для решения данной проблемы:

#include <vector>
#include <iostream>

template<typename S>
class It {
    typedef It<S> self_type;
    const S& span_;
    int ix_;

public:
    It(const S& span, const int ix)
        : span_(span), ix_(ix) {}

    self_type& operator++() {
        ++ix_;
        return *this;
    }

    int& operator*() const {
        return span_[ix_];
    }

    bool operator!=(const self_type& rhs) const {
        return &span_ != &rhs.span_ or ix_ != rhs.ix_;
    }
};

template <typename V>
class span {
    typedef span<V> self_type;
public:
    explicit span(V& v) : v_(v) {}
    It<self_type> begin() const { return It<self_type>(*this, 0); }
    It<self_type> end() const { return It<self_type>(*this, v_.size()); }

    int& operator[](const int ix) const {return v_[ix];}
private:
    V& v_;
};

int main() {
    typedef std::vector<int> V;
    V v(10);
    const span<V> s(v);
    for (auto&& x : s) {
        x = 4;
        std::cout << x << "\n";
    }
}

Обратите также внимание на исправленную реализацию operator!= и на тот факт, что нет необходимости в const и end() не-1043 * версиях. Вы также можете бросить туда cbegin() и cend(). Затем вам нужно работать над добавлением обратно константных итераторов.


Кстати, в случае, если это спасет кого-то от путаницы: в ближайшее время может быть добавлено std::span ( предлагается для C ++ 20 ); и это будет просто пара (pointer-to-first-element, index), а не ваша (pointer-to-container, index) версия.

Другими словами, в качестве параметра шаблона он будет принимать тип элементов, а не контейнер:

span<std::vector<int>> s(v);
// vs
std::span<int> s(v);

Это позволяет потребителям std::span избегать знания о том, какой контейнер находится за кадром (или даже нет контейнера: непрерывная область памяти или массив).

Наконец, вы можете взглянуть на реализацию GSL std::span, чтобы получить некоторое представление о том, как полностью реализовать его (включая второй параметр шаблона о экстенте).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...