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

C++23 std::ranges::enumerate能否自定义索引类型?替代方案有哪些?

问题:如何让枚举视图生成自定义强类型索引?

背景

C++23引入的std::ranges::views::enumerate是备受期待的工具,遍历它时迭代器解引用会得到包含索引和元素引用的元组。根据标准规定,这个索引的类型固定为range_difference_t<Base>,也就是底层范围的差值类型。

但在实际场景中,我们可能会使用自定义容器,比如仅允许用强类型索引访问元素的StrongVector——这种索引是基础整数的包装器,禁止与基础整数的隐式转换,以此避免索引混用的错误。此时std::ranges::enumerate返回的默认索引类型就无法直接用于容器的operator[]或at()方法。

核心疑问

  1. 能否修改std::ranges::enumerate,让它生成自定义的强类型索引?
  2. 如果不行,有哪些可行的替代方案?

场景代码示例

template <class T, class Tag>
struct StrongT {
    explicit StrongT(T t) : _t(t) {} // 禁止隐式构造
    
    T base() const { return _t; }  // 仅提供显式获取基础值的方法

    // 补充必要的算术、比较运算符(如++, ==, <等)

private:
    T _t;
};

// 自定义强类型索引
using MyIdx =  StrongT<int, struct MyTag>;

// 仅允许用MyIdx访问的自定义Vector
using MyVec = StrongVector<std::string, MyIdx>;

int main () {
    MyVec v {"hello", "world"};
    // 此处无法通过编译:decltype(idx)是int(range_difference_t),不是MyIdx
    for ( auto [idx, str] : v | std::ranges::enumerate ) {
        assert(v[idx] == str); 
    }
}

解答

关于能否修改std::ranges::enumerate的索引类型

不能。C++标准明确规定std::ranges::enumerate的索引类型为range_difference_t<Base>,这是该视图的既定行为,无法通过参数或模板特化修改。

替代方案

方案一:自定义枚举视图,控制索引类型

自己实现一个支持自定义索引类型的enumerate视图,完全掌控返回的索引类型。示例代码如下:

#include <ranges>
#include <utility>
#include <iterator>
#include <cassert>

// 自定义强类型索引模板
template <class T, class Tag>
struct StrongT {
    using value_type = T;
    explicit StrongT(T t) : _t(t) {}
    
    T base() const { return _t; }

    // 补充必要的运算符
    bool operator==(const StrongT& other) const = default;

private:
    T _t;
};

using MyIdx = StrongT<int, struct MyTag>;

// 自定义Vector实现
template<class T, class Idx>
struct StrongVector {
    std::vector<T> data_;

    StrongVector(std::initializer_list<T> init) : data_(init) {}

    T& operator[](Idx idx) { return data_[idx.base()]; }
    const T& operator[](Idx idx) const { return data_[idx.base()]; }
};

using MyVec = StrongVector<std::string, MyIdx>;

// 自定义enumerate视图
template<std::ranges::input_range R, class Idx>
struct enumerate_view : std::ranges::view_interface<enumerate_view<R, Idx>> {
private:
    R base_ = R();
public:
    enumerate_view() = default;
    explicit enumerate_view(R r) : base_(std::move(r)) {}

    struct iterator {
        using base_iterator = std::ranges::iterator_t<R>;
        using difference_type = std::ranges::range_difference_t<R>;
        using value_type = std::pair<Idx, std::ranges::range_reference_t<R>>;

        base_iterator it_;
        difference_type pos_ = 0;

        iterator() = default;
        iterator(base_iterator it, difference_type pos) : it_(it), pos_(pos) {}

        value_type operator*() const {
            // 将底层差值类型转换为自定义强类型索引
            return {Idx(static_cast<typename Idx::value_type>(pos_)), *it_};
        }

        iterator& operator++() {
            ++it_;
            ++pos_;
            return *this;
        }

        iterator operator++(int) {
            auto tmp = *this;
            ++*this;
            return tmp;
        }

        bool operator==(const iterator& other) const = default;
    };

    iterator begin() { return {std::ranges::begin(base_), 0}; }
    iterator end() { return {std::ranges::end(base_), std::ranges::distance(base_)}; }
};

// 封装成视图适配器,方便管道调用
namespace views {
    template<class Idx>
    auto enumerate = [](auto&& r) {
        return enumerate_view<std::views::all_t<decltype(r)>, Idx>(std::forward<decltype(r)>(r));
    };
}

int main () {
    MyVec v {"hello", "world"};
    // 使用自定义视图,直接得到MyIdx类型的索引
    for ( auto [idx, str] : v | views::enumerate<MyIdx> ) {
        assert(v[idx] == str); // 正常编译并运行
    }
}

方案二:遍历中显式转换索引类型

如果不想自定义视图,可以在遍历std::ranges::enumerate时,将返回的默认索引显式转换为自定义强类型。这种方式简单直接,但需要手动处理转换:

int main () {
    MyVec v {"hello", "world"};
    for ( auto [raw_idx, str] : v | std::ranges::enumerate ) {
        // 显式构造自定义索引
        MyIdx idx(static_cast<int>(raw_idx));
        assert(v[idx] == str); 
    }
}

方案三:给自定义容器添加枚举成员函数

为StrongVector添加一个成员函数,内部封装自定义枚举视图的调用,让使用更简洁:

template<class T, class Idx>
struct StrongVector {
    // ... 其他代码 ...

    auto enumerate() {
        return views::enumerate<Idx>(*this);
    }
};

// 使用方式
int main () {
    MyVec v {"hello", "world"};
    for ( auto [idx, str] : v.enumerate() ) {
        assert(v[idx] == str); 
    }
}

内容的提问来源于stack exchange,提问作者supernun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.30 01:45:06