Невозможно получить эквивалент std :: less для работы с вложенными итераторами - PullRequest
0 голосов
/ 26 мая 2020

Я пытаюсь написать шаблон вложенного итератора. Идея состоит в том, что вы можете перебирать 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?

1 Ответ

1 голос
/ 26 мая 2020

Частичная специализация default_range для случая const T, где вы не перегружаете проблемные c функции:

template <typename T>
struct default_range<const T> {
    constexpr auto begin(const T& t) const {
        return t.begin();
    }
    constexpr auto end(const T& t) const {
        return t.end();
    }
};

Также обратите внимание, что std::declval может использоваться только в неоцененный контекст. Сказав это, inner_impl необходимо переопределить, например:

template <typename Outer, typename OuterRange>
using inner_impl_t = decltype(*std::declval<OuterRange>().begin(std::declval<Outer&>()));
...