Unreal TArray适配C++20 ranges时视图适配器报错问题问询
我想要让Unreal Engine的TArray兼容C++20 ranges,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} { } // ... 迭代器相关方法 };
完整代码见下文。目前该包装类能通过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) | std::views::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}; }
问题出在以下几个核心方面:
1. 未满足std::ranges::viewable_range约束
范围适配器(如views::transform)要求左侧操作数是可视图范围(viewable_range),即要么是std::ranges::view,要么是满足std::ranges::range的左值范围。你的包装类是左值范围,但迭代器实现存在缺陷,导致适配器无法识别其兼容性。
2. 迭代器实现的关键错误
(1) 缺失difference_type类型别名
标准迭代器要求必须定义difference_type,你的TArrayIterator未提供该类型,导致范围适配器无法正确计算迭代器间距:
template <bool is_const, typename T> class TArrayIterator { public: // ... 原有类型定义 using difference_type = std::ptrdiff_t; // 新增此类型别名 // ... };
(2) 算术运算符构造调用遗漏end参数
operator+/operator-返回的迭代器未传入end指针,导致构造的迭代器m_end为空,破坏迭代器的哨兵判断逻辑:
// 修复前 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, lhs.m_end}; } friend TArrayIterator operator-(TArrayIterator lhs, int rhs) { return TArrayIterator{lhs.m_p - rhs, lhs.m_end}; } friend TArrayIterator operator+(int lhs, TArrayIterator rhs) { return TArrayIterator{rhs.m_p + lhs, rhs.m_end}; }
(3) 冗余的sentinel类型
你定义的sentinel结构体完全多余,因为TArrayIterator本身就是随机访问迭代器,可直接作为自身的哨兵。删除该结构体及其相关的operator==重载,避免编译器混淆哨兵类型判断。
3. 避免悬空引用问题
当前make_range接受右值引用,若传入临时TArray会导致返回的包装类持有悬空引用。修改为只接受左值引用:
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}; }
完成以上修改后,包装类即可满足范围适配器的约束,views::transform能正常工作。
内容的提问来源于stack exchange,提问作者Fantastic Mr Fox

