Пользовательский итератор в диапазоне на основе: проблема с константностью - PullRequest
0 голосов
/ 14 февраля 2019

Следующий код основан на коде, найденном в Современная поваренная книга по программированию на C ++ , и скомпилирован в VS 2017:

#include <iostream>

using namespace std;

template <typename T, size_t const Size> 
class dummy_array 
{ 
    T data[Size] = {}; 

public: 
    T const & GetAt(size_t const index) const 
    { 
        if (index < Size) return data[index]; 
        throw std::out_of_range("index out of range"); 
    } 

    // I have added this
    T & GetAt(size_t const index) 
    { 
        if (index < Size) return data[index]; 
        throw std::out_of_range("index out of range"); 
    } 

    void SetAt(size_t const index, T const & value) 
    { 
        if (index < Size) data[index] = value; 
        else throw std::out_of_range("index out of range"); 
    } 

    size_t GetSize() const { return Size; } 
};

template <typename T, typename C, size_t const Size> 
class dummy_array_iterator_type 
{ 
public: 
    dummy_array_iterator_type(C& collection,  
        size_t const index) : 
        index(index), collection(collection) 
    { } 

    bool operator!= (dummy_array_iterator_type const & other) const 
    { 
        return index != other.index; 
    } 

    T const & operator* () const 
    { 
        return collection.GetAt(index); 
    }

    // I have added this
    T & operator* () 
    { 
        return collection.GetAt(index); 
    } 

    dummy_array_iterator_type const & operator++ () 
    { 
        ++index; 
        return *this; 
    } 

private: 
    size_t   index; 
    C&       collection; 
};

template <typename T, size_t const Size> 
using dummy_array_iterator =  dummy_array_iterator_type<T, dummy_array<T, Size>, Size>; 

// I have added the const in 'const dummy_array_iterator_type'
template <typename T, size_t const Size> 
using dummy_array_const_iterator =  const dummy_array_iterator_type<T, dummy_array<T, Size> const, Size>;

template <typename T, size_t const Size> 
inline dummy_array_iterator<T, Size> begin(dummy_array<T, Size>& collection) 
{ 
    return dummy_array_iterator<T, Size>(collection, 0); 
} 

template <typename T, size_t const Size> 
inline dummy_array_iterator<T, Size> end(dummy_array<T, Size>& collection) 
{ 
    return dummy_array_iterator<T, Size>(collection, collection.GetSize()); 
} 

template <typename T, size_t const Size> 
inline dummy_array_const_iterator<T, Size> begin(dummy_array<T, Size> const & collection) 
{ 
    return dummy_array_const_iterator<T, Size>(collection, 0); 
} 

template <typename T, size_t const Size> 
inline dummy_array_const_iterator<T, Size> end(dummy_array<T, Size> const & collection) 
{ 
    return dummy_array_const_iterator<T, Size>(collection, collection.GetSize()); 
}

int main(int nArgc, char** argv)
{
    dummy_array<int, 10> arr;

    for (auto&& e : arr) 
    { 
        std::cout << e << std::endl; 
        e = 100;    // PROBLEM
    } 

    const dummy_array<int, 10> arr2;

    for (auto&& e : arr2)   // ERROR HERE
    { 
        std::cout << e << std::endl; 
    } 
}

Теперь ошибка указывает на строку

T & operator* ()

с указанием

'return': невозможно преобразовать из 'const T' в 'T &' "

..., которое поднимается изМой цикл на основе диапазона for основан на arr2.

Почему компилятор выбирает неконстантную версию operator*()?. Я долго смотрел на это, думаю, потому что он думает, чтообъект, для которого он вызывает этот оператор, не является константой: это должно быть dummy_array_const_iterator. Но этот объект объявлен константным через

template <typename T, size_t const Size> 
using dummy_array_const_iterator =  const dummy_array_iterator_type<T, dummy_array<T, Size> const, Size>;

... поэтому я действительно непонять, что происходит. Может кто-нибудь уточнить, пожалуйста?

TIA

Ответы [ 2 ]

0 голосов
/ 16 февраля 2019

dummy_array_const_iterator::operator * всегда должен возвращать T const & независимо от константности самого объекта итератора.

Самый простой способ достичь этого, вероятно, просто объявить его с T const как базовый тип значения итератора:

template <typename T, size_t const Size> 
using dummy_array_const_iterator = dummy_array_iterator_type<T const, dummy_array<T, Size> const, Size>;

Поскольку вы возвращаете итератор по значению, его постоянство может быть легко потеряно по правилам вывода типа c ++, и просто объявить dummy_array_const_iterator в качестве псевдонима для const dummy_array_iterator_type недостаточно.то есть следующие ошибки:

#include <type_traits>

struct I { };
using C = I const;
C begin();

int bar()
{
    auto x = begin(); // type of x is deduced as I
    static_assert(std::is_same<I, decltype(x)>::value, "same"); // PASS
    static_assert(std::is_same<decltype(begin()), decltype(x)>::value, "same"); // ERROR
}
0 голосов
/ 14 февраля 2019

Я нашел способ включить T& operator*() только тогда, когда C не является константой:

    template <class Tp = T>
    typename std::enable_if<std::is_const<C>::value, Tp>::type const& operator* () const 
    { 
        return collection.GetAt(index); 
    }

    template <class Tp = T>
    typename std::enable_if<!std::is_const<C>::value, Tp>::type & operator* () const 
    { 
        return collection.GetAt(index); 
    }

Я понятия не имею о синтаксисе (который я получаю из https://stackoverflow.com/a/26678178)

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