基于范围的自定义迭代器:constness问题

Custom iterator in range based for: issue with constness

本文关键字:迭代器 自定义 constness 问题 于范围 范围      更新时间:2023-10-16

以下代码基于现代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* ()

说明

'返回':无法从'const t'转换为't&amp;'"

...是从我的范围基于forarr2上提出的。

为什么编译器选择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

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
}

我才找到了仅当C不是恒定时启用T& operator*()的方法:

    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)