My objective is to take some of the Unreal Engine types and make them possible to use with c++20 ranges. I started with TArray
I figured it would be the easiest. It is essentailly a std::vector
, stored contiguously etc. So in all it can easily be represented with this:
template <typename T, typename A = int>
struct TArray {
T* GetData() const;
size_t Num() const;
};
I have basically made a wrapper for this that looks a bit like:
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}
{
}
...
I will put the full code at the end of the question. You can also play with the toy example here: https://godbolt.org/z/EEEzPWeG7
Basically i seem to have a fully compatible range array, it works with std::range algorithms. It passes checks such as:
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>);
But when i use it like this:
auto t = make_range(tarr) | v::transform([](auto i) { return i + 1;});
I get a nice big failure, i don’t meet the constraints for the range adapter operator.
Gcc suggests:
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>}]
I cannot find any documentation about these constraints. Does anyone know what i am doing wrong?
Full code incoming
#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};
}