Моя цель — взять некоторые типы 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};
}
@Red.Wave хорошее предложение, но я не могу изменить код ue





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