You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unreal TArray适配C++20 ranges时视图适配器报错问题问询

问题: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 01:05:55