【问题标题】:Custom iterator in range based for: issue with constness基于范围的自定义迭代器:常量问题
【发布时间】:2019-02-14 12:58:29
【问题描述】:

以下代码基于Modern C++ programming cookbook中的代码,并在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

【问题讨论】:

  • 我认为您应该创建另一种类型的迭代器,或者将原始迭代器重命名为 const_dummy_array_iterator_type 并拥有另一个非常量版本,就像在 C++ 标准库中一样 (const_iteratoriterator)

标签: c++ c++11


【解决方案1】:

dummy_array_const_iterator::operator * 应始终返回 T const &amp;,而不管迭代器对象本身的常量。

实现这一点的最简单方法可能就是将其声明为 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++ 类型推导规则 lost 并且仅将 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
}

【讨论】:

  • 好的,我试过了,它可以编译。如果我将operator*()=const dummy_array_iterator_type 更改为= dummy_array_iterator_type'. But I still don't understand: the const` 形式,它甚至可以编译应该 已在const dummy_array_iterator_type 上调用但它不是:为什么? 即使dummy_array_iterator_type 不是const,这段代码现在如何促进dummy_array_iterator_type::operator*()const 版本被调用?您能否详细说明您的答案(您有更多空间)?
  • @Wad 我在答案中添加了一个 constness loss 的例子。希望这可以澄清观察到的行为。
  • 谢谢,我明白auto 是如何丢弃const 的。 1)因此我假设range for 将使用auto 关键字来推断迭代器的类型,因此出现了这个问题;它是否正确? 2) 我认为您的修复会导致调用 'operator*()' 的 'const' 形式,但是已经通过代码它没有!;始终调用的是 none-const 形式;我可以完全删除const 表单!我现在完全糊涂了,您能否具体说明您的修复方法有什么帮助?
  • 1) 是的,这种行为在标准中指定 - en.cppreference.com/w/cpp/language/range-for#Explanation 2) 如果你想要与标准库迭代器相同的行为,那么你应该只留下 const 形式(并删除删除非常量) - 将为 const 和非 const 对象调用它,如果您对 const 对象进行相反的解引用,则不会编译。
  • 抱歉,想了想。您的解决方法是将类型基础值类型更改为const,因为在我的代码中,对于常量迭代器,dummy_array_iterator_type::operator*()(即T&amp;)的返回类型变为 const T&amp;const int&amp;; dummy_array_iterator_type 本身不是 const 并不重要。因此,尝试分配给const dummy_array 的迭代器将失败,因为我们试图分配给const int&amp;。当dummy_array 不是const 时,operator*() 返回一个int&amp;,以便我们可以分配给它!请确认这是正确的吗?
【解决方案2】:

我找到了一种仅在C 不恒定时启用T&amp; 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 得到的)

【讨论】:

  • 谢谢;这是一个很好的解决方法。但是,我可能会为此提供赏金,因为我需要了解为什么我的代码原样不起作用;请不要被冒犯!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-05-10
  • 2011-06-01
  • 2016-04-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-12-21
相关资源
最近更新 更多