编辑__:增加了为什么要这样做的说明,并更新了代码,因为我还没有任何答案
我有一个类似C++11数组的类,它可以是一个随机访问迭代器的包装器。基于索引的访问和.begin()/.end()可以直接传递给迭代器,但是当对象是const时会有一些复杂:
template
class Storage : public Size {
DataIterator iterator;
public:
Storage(const DataIterator &iterator, const Size &size) : Size(size), iterator(iterator) {}
auto operator[](index_t i)
-> decltype(iterator[i]) {
return iterator[i];
}
auto operator[](index_t i) const
-> MakeConst {
return iterator[i];
}
DataIterator begin() {return iterator;}
ConstWrapper begin() const {return iterator;}
DataIterator end() {return iterator + this->size();}
ConstWrapper end() const {return iterator + this->size();}
};如果我们简单地从const版本的.begin()和.end()返回D6,那么持有Storage const &的人就会(错误地!)能够通过该迭代器修改数组。
像std::vector这样的容器有两个独立的迭代器(::iterator和::const_iterator),但是我们的Storage类只有读写类,所以我们使用ConstWrapper来合成一个:
template
class ConstWrapper {
Iterator iterator;
using traits = std::iterator_traits;
public:
using difference_type = typename traits::difference_type;
using value_type = typename traits::value_type;
using pointer = ConstWrapper;
using reference = MakeConst;
using iterator_category = typename traits::iterator_category;
ConstWrapper() {}
ConstWrapper(const Iterator &iterator) : iterator(iterator) {}
// The problematic cases:
auto operator[](index_t i) const
-> MakeConst {
return iterator[i];
}
auto operator*() const
-> MakeConst {
return *iterator;
}
bool operator!= (const ConstWrapper& other) const {
return iterator != other.iterator;
}
/** All the other random-access iterator methods **/
};
// Specialisation to prevent infinite loops
template
class ConstWrapper> : ConstWrapper {
public:
using ConstWrapper::ConstWrapper;
};该实现转发了所有相关方法,并对ConstWrapper>进行了专门化,使其不能自行包装。
关键部分是MakeConst,它将类型转换为正确的const变量(与简单添加const不同,后者对引用没有影响):
// Converts (T & -> T const &), and (T -> const T)
using MakeConst = typename std::conditional<
std::is_reference::value,
typename std::remove_reference::type const &,
const T
>::type;这有道理吗,还好吗?我还能做些更易读/更有效率的事情吗?
谢谢!
发布于 2020-10-25 06:30:50
我不认为你的专长是做你认为它正在做的事。https://godbolt.org/z/Gfh7sv
您不希望ConstWrapper>从ConstWrapper继承;这意味着CW>“是一种”CW,这不是真的。你不想在这里建立继承关系。您想要的只是Storage的专门化,而不是包装DataIterators (碰巧已经是ConstWrapper的专门化)。您这样做的方式是使用额外的一层间接别名:
template struct maybe_constwrap { using type = ConstWrapper; };
template struct maybe_constwrap> { using type = ConstWrapper; };
template
class Storage : public Size {
public:
using iterator = DataIterator;
using const_iterator = typename maybe_constwrap::type;
~~~
};现在,如果DataIterator已经是一个ConstWrapper,那么iterator和const_iterator实际上都是相同的类型.这就是你想要的。
您的MakeConst似乎是建立在一个可疑的想法之上的,即const _Bit_reference是不可分配的。这可能会在C++23中发生变化。有关某些上下文,请参见https://stackoverflow.com/questions/63412623/should-c20-stdrangessort-not-need-to-support-stdvectorbool。
在operator*和operator[]的返回类型中重复一些元编程。它们应该只返回前面已经计算过的reference类型。
reference operator*() const { return *it_; }顺便说一句,我正在将您的数据成员的名称从iterator更改为it_,因为我怀疑有一个名为iterator的成员的容器级类型是一个糟糕的想法,它的成员名为(A)不公开,(B)不是一个类型。
您的operator!=可能应该使用隐藏的朋友成语:
friend bool operator==(ConstWrapper a, ConstWrapper b) { return a.it_ == b.it_; }
friend bool operator!=(ConstWrapper a, ConstWrapper b) { return a.it_ != b.it_; }在C++20中,理论上您可以省略operator!=,只提供operator==。我还不知道这是不是个好主意。
通过值传递ConstWrapper应该很好,因为它只包含一个迭代器,而且迭代器的复制成本很低。
https://codereview.stackexchange.com/questions/250502
复制相似问题