如何创建自定义C++ range/view?编译器PoC词法分析场景问题
实现兼容C标准库的自定义Token View(基于C20 Ranges)
问题根源分析
你遇到的管道运算符错误,核心原因是自定义的MyView未满足C++20 Ranges的两个关键概念:
std::ranges::range:编译器无法将MyView&识别为合法的范围类型,因为迭代器的类型定义不符合标准,且MyView的接口未满足range的约束。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
相关产品推荐
相关产品推荐

