Я пытаюсь написать шаблон вложенного итератора. Идея состоит в том, что вы можете перебирать int
s из std::array<std::array<int, N> M>>
, как если бы это был один непрерывный массив. Но шаблон также будет работать со всеми видами других комбинаций.
Вот упрощенная версия моего кода, в которой сейчас есть небольшая проблема:
#include <array>
#include <type_traits>
#include <optional>
template <typename T>
struct default_range {
constexpr auto begin(const T& t) const {
return t.begin();
}
constexpr auto end(const T& t) const {
return t.end();
}
constexpr auto begin(T& t) const {
return t.begin();
}
constexpr auto end(T& t) const {
return t.end();
}
};
template <typename Outer, typename OuterRange>
constexpr auto inner_impl() {
// IMPORTANT: this line is necessary because otherwise OuterRange will always be invoked with the const& version of
// begin().
// Outer must first be captured in a variable so that we preserve constness properly
Outer outer = std::declval<Outer>();
return *std::declval<OuterRange>().begin(outer);
}
template <typename Outer, typename OuterRange>
using inner_t = std::remove_reference_t<decltype(inner_impl<Outer, OuterRange>())>;
template <typename T>
using iterator_value_t = typename std::iterator_traits<T>::value_type;
template <typename OuterIterator,
typename InnerRange = default_range<std::remove_reference_t<iterator_value_t<OuterIterator>>>>
class nested_iterator {
private:
using InnerIterator = decltype (std::declval<InnerRange>().begin(*std::declval<OuterIterator>()));
public:
using self_type = nested_iterator;
using value_type = iterator_value_t<InnerIterator>;
using reference = std::remove_reference_t<value_type> &;
using pointer = std::remove_reference_t<value_type> *;
using iterator_category = std::forward_iterator_tag;
using difference_type = size_t;
private:
struct inner_iterators {
InnerIterator pos;
InnerIterator end;
constexpr inner_iterators(InnerIterator pos, InnerIterator end)
: pos{std::move(pos)}, end{std::move(end)}
{
}
};
OuterIterator outerPos;
OuterIterator outerEnd;
std::optional<inner_iterators> inners = std::nullopt;
InnerRange innerRange;
public:
constexpr nested_iterator(OuterIterator outer, OuterIterator outerEnd)
: outerPos{std::move(outer)}, outerEnd{std::move(outerEnd)}
{
// constructor code here
}
// operator overloads here
}; // class iterator
template <typename Outer,
typename OuterRange = default_range<Outer>,
typename InnerRange = default_range<inner_t<Outer, OuterRange>>>
class nested_iterable {
private:
using Inner = inner_t<Outer, OuterRange>;
public:
using outer_iterator = decltype (std::declval<OuterRange>().begin(std::declval<Outer>()));
using inner_iterator = decltype (std::declval<InnerRange>().begin(std::declval<Inner>()));
using iterator = nested_iterator<outer_iterator, InnerRange>;
private:
Outer *outer;
OuterRange outerRange;
public:
constexpr nested_iterable(Outer &outer) : outer{&outer} {}
constexpr iterator begin() const
{
return {outerRange.begin(*outer), outerRange.end(*outer)};
}
constexpr iterator end() const
{
return {outerRange.end(*outer), outerRange.end(*outer)};
}
};
constexpr void test()
{
const std::array<std::array<int, 5>, 3> arr{};
nested_iterable range{arr};
}
<source>:13:20: error: multiple overloads of 'begin' instantiate to the same signature 'auto (const std::array<std::array<int, 5>, 3> &) const'
constexpr auto begin(T& t) const {
^
<source>:27:39: note: in instantiation of template class 'default_range<const std::array<std::array<int, 5>, 3> >' requested here
return *std::declval<OuterRange>().begin(outer);
...
<source>:110:21: note: while substituting deduced template arguments into function template '<deduction guide for nested_iterable>' [with Outer = const std::array<std::array<int, 5>, 3>, OuterRange = (no value), InnerRange = (no value)]
nested_iterable range{arr};
Как один можно легко сказать, default_range
не может быть создан для const std::array
, потому что методы begin
и end
становятся неоднозначными при создании экземпляра с типом const
.
Идея default_range
состоит в том, что он работает как std::less
по умолчанию, но пользователь может решить создать свой собственный, вызвав вместо него rbegin
.
Это, похоже, недостающий фрагмент головоломки прямо сейчас. Я также пробовал использовать один begin
вместо перегрузок для обоих типов ссылок, но это тоже не работает. В этом случае T
всегда должен быть rvalue-ссылкой.
Итак, как я могу реализовать default_range
, чтобы он работал с типами const
и не const
?