Адаптируйте существующий тип диапазона для совместимости с диапазонами C++20, не меняя его

Моя цель — взять некоторые типы Unreal Engine и сделать их возможными для использования с диапазонами C++20. Я начал с TArray и решил, что это будет проще всего. По сути, это std::vector, хранящийся рядом и т. д. Итак, в целом это можно легко представить следующим образом:

template <typename T, typename A = int>
struct TArray {
    T* GetData() const;
    size_t Num() const;
};

По сути, я сделал для этого обертку, которая выглядит примерно так:

        template <template <typename, typename> typename TCont, typename T, typename TAlloc>
        class rangified_array
        {
        public:
            using ue_container = TCont<T, TAlloc>;

            using value_type             = T;
            using allocator_type         = TAlloc;
            ...

            rangified_array(ue_container& container)
                : m_container{container}
            {
            }

    ...

Я помещу полный код в конце вопроса. Вы также можете поиграть с игрушечным примером здесь: https://godbolt.org/z/EEEzPWeG7

По сути, у меня, кажется, есть полностью совместимый массив диапазонов, он работает с алгоритмами std::range. Он проходит такие проверки, как:

using r_t = decltype(make_range(tarr));
static_assert(std::sentinel_for<r_t::iterator, r_t::iterator>);
static_assert(std::ranges::range<r_t>);

Но когда я использую его так:

auto t = make_range(tarr) | v::transform([](auto i) { return i + 1;}); 

Я получаю большую ошибку: я не соответствую ограничениям для оператора адаптера диапазона. Gcc предлагает:

ranges:955:5: note: constraints not satisfied
ranges: In substitution of 'template<class _Self, class _Range>  requires (__is_range_adaptor_closure<_Self>) && (__adaptor_invocable<_Self, _Range>) constexpr auto std::ranges::views::__adaptor::operator|(_Range&&, _Self&&) [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, foo()::<lambda(auto:10)> >; _Range = detail::rangified<TArray<int>, int, int>]':
<source>:201:77:   required from here
  201 |         auto t = make_range(tarr) | v::transform([](auto i) { return i + 1;});
      |                                                                             ^
ranges:914:13:   required for the satisfaction of '__adaptor_invocable<_Self, _Range>' [with _Self = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, foo::._anon_113>; _Range = detail::rangified<TArray<int, int>, int, int>]
ranges:915:9:   in requirements  [with _Adaptor = std::ranges::views::__adaptor::_Partial<std::ranges::views::_Transform, foo::._anon_113>; _Args = {detail::rangified<TArray<int, int>, int, int>}]

Я не могу найти никакой документации об этих ограничениях. Кто-нибудь знает, что я делаю неправильно?

Полный код приходит

#include <ranges>

template <typename T, typename A = int>
struct TArray {

    T* GetData() const;
    size_t Num() const;

};

    
    namespace detail
    {
        // Currently only the TArray is supported. If you need to range adapt something else you can add the
        // specialisation below.

        template <typename TCont, typename T, typename TAlloc>
        class rangified;

       struct sentinal
       {
       };

        template <bool is_const, typename T>
        class TArrayIterator
        {
        public:
            TArrayIterator() = default;

            explicit TArrayIterator(const T* p, const T* end)
                : m_p{const_cast<T*>(p)}, m_end{end} {}

            explicit TArrayIterator(T* p, const T* end)
                requires (!is_const)
                : m_p{p}, m_end{end} {}

            using value_type        = T;
            using element_type      = T;
            using iterator_category = std::contiguous_iterator_tag;

            T*              operator->() const { return m_p; }
            T&              operator*() const { return *m_p; }
            T&              operator[](size_t p) const { return *(m_p + p); }
            TArrayIterator& operator++()
            {
                ++m_p;
                return *this;
            }

            TArrayIterator operator++(int)
            {
                TArrayIterator out = *this;
                ++(*this);
                return out;
            }

            TArrayIterator& operator--()
            {
                --m_p;
                return *this;
            }

            TArrayIterator operator--(int)
            {
                TArrayIterator out = *this;
                --(*this);
                return out;
            }

            TArrayIterator& operator+=(size_t i)
            {
                m_p += i;
                return *this;
            }

            TArrayIterator& operator-=(size_t i)
            {
                m_p -= i;
                return *this;
            }


friend bool operator==(TArrayIterator lhs, sentinal) {
    return lhs.m_p == lhs.m_end;
}

friend bool operator==(sentinal, TArrayIterator rhs ) {
    return rhs.m_p == rhs.m_end;
}
            friend auto operator<=>(TArrayIterator, TArrayIterator) = default;
            friend bool operator==(TArrayIterator, TArrayIterator) = default;
            friend std::ptrdiff_t operator-(TArrayIterator lhs, TArrayIterator rhs) { return lhs.m_p - rhs.m_p; }
            friend TArrayIterator operator+(TArrayIterator lhs, int rhs) { return TArrayIterator{lhs.m_p + rhs}; }
            friend TArrayIterator operator-(TArrayIterator lhs, int rhs) { return TArrayIterator{lhs.m_p - rhs}; }
            friend TArrayIterator operator+(int lhs, TArrayIterator rhs) { return TArrayIterator{lhs + rhs.rhs}; }

        private:
            T* m_p = nullptr;
            const T* m_end = nullptr;
        };

        template <template <typename, typename> typename TCont, typename T, typename TAlloc>
        class rangified_array
        {
        public:
            using ue_container = TCont<T, TAlloc>;

            using value_type             = T;
            using allocator_type         = TAlloc;
            using size_type              = std::size_t;
            using difference_type        = std::ptrdiff_t;
            using reference              = T&;
            using const_reference        = const T&;
            using pointer                = T*;
            using const_pointer          = const T*;
            using iterator               = TArrayIterator<false, T>;
            using const_iterator         = TArrayIterator<true, T>;
            using reverse_iterator       = std::reverse_iterator<iterator>;
            using const_reverse_iterator = std::reverse_iterator<const_iterator>;

            rangified_array(ue_container& container)
                : m_container{container}
            {
            }

            const_iterator begin() const { return const_iterator{m_container.GetData(), m_container.GetData() +  + m_container.Num()}; }
            const_iterator end() const { 
                auto last = m_container.GetData() + m_container.Num();
                return const_iterator{last, last };
            }

            iterator begin() { return iterator{
                m_container.GetData(), m_container.GetData()  + m_container.Num()}; 
                }
            iterator end() { 
                auto last = m_container.GetData() + m_container.Num();
                return iterator{last, last };}

        private:


            ue_container& m_container;
        };

        // This provides a specialisation for the const and non const TArray overloads
        template <typename T, typename TAlloc>
        class rangified<TArray<T, TAlloc>, T, TAlloc> : public rangified_array<TArray, T, TAlloc>
        {
            using rangified_array<TArray, T, TAlloc>::rangified_array;
        };

        template <typename TUeComponent>
        struct ue_underlying_types;

        template <typename T, typename TAlloc>
        struct ue_underlying_types<TArray<T, TAlloc>>
        {
            using container_type = TArray<T, TAlloc>;
            using value_type     = T;
            using allocator_type = TAlloc;
        };

        template <typename T>
        struct is_range_adapter_supported : public std::false_type
        {
        };

        template <typename T, typename TAlloc>
        struct is_range_adapter_supported<TArray<T, TAlloc>> : public std::true_type
        {
        };

        template <typename T>
        constexpr inline bool is_range_adapter_supported_v = is_range_adapter_supported<T>::value;

    }

    template <typename TUeContainer>
        requires detail::is_range_adapter_supported_v<std::remove_cv_t<std::remove_reference_t<TUeContainer>>>
    auto make_range(TUeContainer&& arr)
    {
        using T = detail::ue_underlying_types<std::remove_reference_t<TUeContainer>>;
        using R = detail::rangified<typename T::container_type, typename T::value_type, typename T::allocator_type>;
        return R{arr};
    }

Быстрый патч был бы auto TArray::to_span()-> std::span<T>;. Теперь вы можете вызывать функцию-член to_span() всякий раз, когда вам нужно что-то с элементами. Но сделать это бесплатной функцией friend или оператором преобразования explicit может быть удобнее.

Red.Wave 16.05.2024 13:01

@Red.Wave хорошее предложение, но я не могу изменить код ue

Fantastic Mr Fox 16.05.2024 13:34
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
2
63
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Я получаю серьезную ошибку: я не соответствую ограничениям для оператора адаптера диапазона.

Ты прав. Примечание [range.refinements] стр.6:

Концепция viewable_range определяет требования к типу диапазона, который можно безопасно преобразовать в представление.

Такое преобразование имеет место, когда, например, ваш диапазон попадает в std::views::all, что происходит из-за руководства по вычету std::ranges::transform_view. Ваш rangifed_array удовлетворяет большинству требований, но не справляется с этим:

(!view<remove_cvref_t<T>> &&
    (is_lvalue_reference_v<T> ||
        (movable<remove_reference_t<T>> && !is-initializer-list<T>))

Либо вашему rangified_array нужно movable (чего нет, потому что его нельзя назначить, поскольку вы храните ссылку), либо вам нужно передать что-то как lvalue:

auto t = v::transform(r, add_1);

См. Compiler Explorer. Все компиляторы это допускают (после удаления опечатки rhs.rhs).

Всегда делать последнее довольно глупо, поэтому просто не храните ссылку и сделайте свой класс более похожим на диапазон. На самом деле, я не совсем понимаю, почему бы просто не добавить конверсию с TArray на std::span. Если вам отчаянно нужен собственный тип, вы можете сохранить указатель вместо ссылки и таким образом сделать свой тип назначаемым.

Удивительно, но и ответ, и то, что соответствующую ошибку так сложно проанализировать. Я не думал о размахе, идее, но это здорово. Некоторые из этих концепций мне все еще понадобятся, когда я буду создавать диапазон для TMap и других.

Fantastic Mr Fox 16.05.2024 12:22

Другие вопросы по теме

Похожие вопросы

Ошибка: нет соответствия для 'operator<<' (типы операндов: 'QTextStream' и 'const char [3]')
В C++ может ли поток, ожидающий условной переменной, уведомить себя?
Обработка деления числа double на большую степень 2 в C++
Многопоточный push_back в std::vector: мьютекс, увеличить и отредактировать на месте или создать вектор для результатов и отправить его обратно?
Специализация класса C++ Enable_if не вызывается
Почему od и мой код на C++ читаются с другим порядком байтов, чем тот, который отображается шестнадцатеричными редакторами?
Существуют ли в C++ «функции массового перемещения», которые избегают множественных назначений перемещения, точно так же, как std::memmove избегает множественных назначений копирования?
Влияйте на выравнивание классов, чтобы плотно упаковывать переменные
Ошибка C3867: нестандартный синтаксис; используйте '&', чтобы создать указатель на член
Почему изменяемая лямбда преобразуется в указатель на функцию вместо вызова оператора()?