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

如何创建自定义C++ range/view?编译器PoC词法分析场景问题

实现兼容C标准库的自定义Token View(基于C20 Ranges)

问题根源分析

你遇到的管道运算符错误,核心原因是自定义的MyView未满足C++20 Ranges的两个关键概念:

  1. std::ranges::range:编译器无法将MyView&识别为合法的范围类型,因为迭代器的类型定义不符合标准,且MyView的接口未满足range的约束。
  2. std::ranges::view:你的类未满足view的核心要求(可默认构造、可移动),导致无法与标准库的range适配器(如std::views::take)通过管道运算符组合。

分步修正方案

1. 修正自定义迭代器,满足std::input_iterator概念

标准输入迭代器要求difference_type为std::ptrdiff_t(而非int),同时确保所有运算符符合规范:

#include <ranges>
#include <iostream>
#include <iterator> // 引入ptrdiff_t
using namespace std;

class Iterator
{
public:
    using iterator_category = std::input_iterator_tag;
    using value_type = int;
    using difference_type = std::ptrdiff_t; // 修正为标准类型
    using pointer = const int*;
    using reference = const int&;

    Iterator(int value = 0) : m_value{ value } {} // 添加默认构造

    reference operator*() const { return m_value; }
    pointer operator->() const { return &m_value; }

    Iterator& operator++()
    {
        ++m_value;
        return *this;
    }

    Iterator operator++(int)
    {
        Iterator tmp{ *this };
        ++(*this);
        return tmp;
    }

    friend bool operator==(const Iterator& a, const Iterator& b)
    {
        return a.m_value == b.m_value;
    }

    // 移除operator!=,依赖C++20的默认!=(基于==)
private:
    int m_value;
};

2. 修正MyView,满足std::ranges::view概念

继承std::ranges::view_interface后,需要补充默认构造函数,并显式标记类为view(通过std::enable_view特化):

class MyView : public std::ranges::view_interface<MyView>
{
public:
    MyView() = default; // 补充默认构造,满足view要求
    MyView(int start, int end) : m_start(start), m_end(end) {}

    auto begin() const { return Iterator(m_start); }
    auto end() const { return Iterator(m_end); }

private:
    int m_start = 0;
    int m_end = 10;
};

// 显式特化,告诉编译器MyView是一个view
template<>
inline constexpr bool std::ranges::enable_view<MyView> = true;

3. 验证修正后的代码

此时管道运算符可以正常工作:

int main()
{
    MyView view_over_v(0, 10);
    for (int n : view_over_v | std::views::take(2))
        std::cout << n << ' '; // 输出:0 1
    std::cout << '\n';
}

适配到词法分析器(Token View)

针对你的编译器PoC,将Lexer改造为兼容Ranges的Token View,核心是让迭代器持有词法分析的状态(DFA、当前输入位置、剩余字符串),实时生成Token:

核心思路

  • 迭代器内部持有string_view、DFA引用、当前解析位置,每次++时执行词法分析,生成下一个Token。
  • View类持有输入的string_view和DFA,begin()返回初始状态的迭代器,end()返回标记解析完成的哨兵迭代器。

简化示例代码

#include <ranges>
#include <string_view>
#include <iostream>

// 假设你的Token类型定义
struct Token {
    enum class Type { Identifier, Keyword, Number } type;
    std::string_view value;

    friend std::ostream& operator<<(std::ostream& os, const Token& tok) {
        os << "Token{type:" << static_cast<int>(tok.type) << ", value:" << tok.value << "}";
        return os;
    }
};

// 假设你的DFA类型
struct DFA {
    // 简化的词法分析逻辑:从pos开始解析,返回Token和新的pos
    std::pair<Token, size_t> next_token(std::string_view input, size_t pos) const {
        // 这里替换为你的实际DFA解析逻辑
        if (pos >= input.size()) {
            return {{Token::Type::Identifier, ""}, input.size()};
        }
        size_t end = input.find_first_of(" \t\n", pos);
        if (end == std::string_view::npos) end = input.size();
        return {{Token::Type::Identifier, input.substr(pos, end - pos)}, end};
    }
};

// Token输入迭代器
class TokenIterator {
public:
    using iterator_category = std::input_iterator_tag;
    using value_type = Token;
    using difference_type = std::ptrdiff_t;
    using pointer = const Token*;
    using reference = const Token&;

    // 哨兵迭代器构造(标记end)
    TokenIterator() = default;
    // 正常迭代器构造
    TokenIterator(const DFA& dfa, std::string_view input)
        : m_dfa(&dfa), m_input(input), m_pos(0) {
        // 生成第一个Token
        if (m_pos < m_input.size()) {
            auto [tok, new_pos] = m_dfa->next_token(m_input, m_pos);
            m_current_token = tok;
            m_pos = new_pos;
        }
    }

    reference operator*() const { return m_current_token; }
    pointer operator->() const { return &m_current_token; }

    TokenIterator& operator++() {
        if (m_pos < m_input.size()) {
            auto [tok, new_pos] = m_dfa->next_token(m_input, m_pos);
            m_current_token = tok;
            m_pos = new_pos;
        } else {
            // 切换到哨兵状态
            *this = TokenIterator();
        }
        return *this;
    }

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

    friend bool operator==(const TokenIterator& a, const TokenIterator& b) {
        // 哨兵迭代器与所有已完成的迭代器相等
        return (a.m_dfa == nullptr && b.m_dfa == nullptr) ||
               (a.m_pos >= a.m_input.size() && b.m_pos >= b.m_input.size());
    }

private:
    const DFA* m_dfa = nullptr;
    std::string_view m_input;
    size_t m_pos = 0;
    Token m_current_token;
};

// Token View类
class TokenView : public std::ranges::view_interface<TokenView> {
public:
    TokenView() = default;
    TokenView(const DFA& dfa, std::string_view input) : m_dfa(&dfa), m_input(input) {}

    auto begin() const { return TokenIterator(*m_dfa, m_input); }
    auto end() const { return TokenIterator(); }

private:
    const DFA* m_dfa = nullptr;
    std::string_view m_input;
};

// 标记为标准view
template<>
inline constexpr bool std::ranges::enable_view<TokenView> = true;

// 测试代码
int main() {
    DFA dfa;
    std::string_view source = "var x = 42;";

    TokenView tokens(dfa, source);
    // 使用管道运算符组合适配器
    for (const auto& tok : tokens | std::views::take(3)) {
        std::cout << tok << '\n';
    }
}

关键注意事项

  • 输入迭代器特性:TokenIterator是输入迭代器,只能单向遍历,不能随机访问,符合词法分析的流式处理场景。
  • 状态管理:迭代器持有DFA和输入的引用,避免拷贝开销,同时确保线程安全(如果需要多线程解析,需调整状态持有方式)。
  • 兼容性:通过满足std::ranges::view和std::input_iterator概念,TokenView可以无缝对接标准库的range适配器(如filter、transform),也可以直接传递给Parser作为输入。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 04:12:03