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

如何为std::span实现remove_prefix并通用化parse函数?

问题与解决方案

背景需求

原本希望使用std::basic_string_view<Token>作为解析器输入,但由于Token并非平凡类,无法满足std::basic_string_view对元素类型的要求,因此改用std::span<Token>。需要实现类似std::basic_string_view::remove_prefix的功能,用于逐个匹配并消耗Token元素。

派生std::span的编译错误及修复

尝试从std::span派生自定义Span类并添加remove_prefix成员函数时,出现编译错误:

[ 50.0%] g++.exe -Wall -std=c++20 -fexceptions -g  -c F:\code\test_crtp_twoargs\main.cpp -o obj\Debug\main.o
F:\code\test_crtp_twoargs\main.cpp: In member function 'constexpr void Span<T>::remove_prefix(std::size_t)':
F:\code\test_crtp_twoargs\main.cpp:52:17: error: there are no arguments to 'subspan' that depend on a template parameter, so a declaration of 'subspan' must be available [-fpermissive]
   52 |         *this = subspan(n);
      |                 ^~~~~~~
F:\code\test_crtp_twoargs\main.cpp:52:17: note: (if you use '-fpermissive', G++ will accept your code, but allowing the use of an undeclared name is deprecated)
F:\code\test_crtp_twoargs\main.cpp: In instantiation of 'constexpr void Span<T>::remove_prefix(std::size_t) [with T = Token; std::size_t = long long unsigned int]':
F:\code\test_crtp_twoargs\main.cpp:113:29:   required from 'constexpr bool char_<T>::visit(v&) const & [with T = Token; v = Span<Token>]'
F:\code\test_crtp_twoargs\main.cpp:125:24:   required from 'constexpr bool parse(Span<T>&, const Parser&) [with Parser = char_<Token>; T = Token]'
F:\code\test_crtp_twoargs\main.cpp:141:10:   required from here
F:\code\test_crtp_twoargs\main.cpp:52:24: error: 'subspan' was not declared in this scope, and no declarations were found by argument-dependent lookup at the point of instantiation [-fpermissive]
   52 |         *this = subspan(n);
      |                 ~~~~~~~^~~
F:\code\test_crtp_twoargs\main.cpp:52:24: note: declarations in dependent base 'std::span<Token, 18446744073709551615>' are not found by unqualified lookup
F:\code\test_crtp_twoargs\main.cpp:52:24: note: use 'this->subspan' instead
F:\code\test_crtp_twoargs\main.cpp:52:15: error: no match for 'operator=' (operand types are 'Span<Token>' and 'std::span<Token, 18446744073709551615>')
   52 |         *this = subspan(n);
      |         ~~~~~~^~~~~~~~~~~~
F:\code\test_crtp_twoargs\main.cpp:44:7: note: candidate: 'constexpr Span<Token>& Span<Token>::operator=(const Span<Token>&)'
   44 | class Span : public std::span<T>
      |       ^~~~
F:\code\test_crtp_twoargs\main.cpp:44:7: note:   no known conversion for argument 1 from 'std::span<Token, 18446744073709551615>' to 'const Span<Token>&'
F:\code\test_crtp_twoargs\main.cpp:44:7: note: candidate: 'constexpr Span<Token>& Span<Token>::operator=(Span<Token>&&)'
F:\code\test_crtp_twoargs\main.cpp:44:7: note:   no known conversion for argument 1 from 'std::span<Token, 18446744073709551615>' to 'Span<Token>&&'

根据提示修改remove_prefix函数后,代码可正常编译,修复后的代码如下:

constexpr void remove_prefix(std::size_t n) {
    auto& self = static_cast<std::span<T>&>(*this);
    self = self.subspan(n);
}

通用parse函数的问题与解决方法

希望让parse函数同时接受std::string_view和自定义Span<Token>类型的输入,修改后的parse函数如下:

template <typename Parser, typename T>
constexpr bool parse(ViewerT<T> &input, Parser const& parser) noexcept {
    return parser.visit(input);
}

但出现模板参数推导失败的编译错误:

[ 50.0%] g++.exe -Wall -std=c++20 -fexceptions -g  -c F:\code\test_crtp_twoargs\main.cpp -o obj\Debug\main.o
F:\code\test_crtp_twoargs\main.cpp: In function 'int main()':
F:\code\test_crtp_twoargs\main.cpp:143:24: error: no matching function for call to 'parse(Span<Token>&, char_<Token>&)'
  143 |     bool result = parse(token_stream_view, p);
      |                   ~~~~~^~~~~~~~~~~~~~~~~~~~~~
F:\code\test_crtp_twoargs\main.cpp:125:16: note: candidate: 'template<class Parser, class T> constexpr bool parse(ViewerT<T>&, const Parser&)'
  125 | constexpr bool parse(ViewerT<T> &input, Parser const& parser) noexcept {
      |                ^~~~~
F:\code\test_crtp_twoargs\main.cpp:125:16: note:   template argument deduction/substitution failed:
F:\code\test_crtp_twoargs\main.cpp:143:24: note:   couldn't deduce template parameter 'T'
  143 |     bool result = parse(token_stream_view, p);
      |                   ~~~~~^~~~~~~~~~~~~~~~~~~~~~

解决方法

方案1:使用C++20概念(Concept)约束输入类型

定义一个Viewer概念,约束所有符合解析器输入要求的类型(包括std::string_view和自定义Span):

#include <concepts>

template<typename V>
concept Viewer = requires(V v) {
    // 要求类型有value_type嵌套类型
    typename V::value_type;
    // 要求支持remove_prefix方法
    { v.remove_prefix(std::size_t{}) } -> std::same_as<void>;
    // 可根据解析器需求添加其他必要接口,比如empty()、front()等
    { v.empty() } -> std::same_as<bool>;
    { v.front() } -> std::convertible_to<const typename V::value_type&>;
};

修改parse函数,直接使用Viewer概念作为输入类型约束:

template <typename Parser, Viewer V>
constexpr bool parse(V &input, const Parser& parser) noexcept {
    return parser.visit(input);
}

这样编译器可以自动推导模板参数,无需显式指定类型。

方案2:取消绑定T的模板别名,直接接受任意输入类型

如果不需要提前绑定T,可以直接将parse函数改为接受任意类型V,只要该类型能被Parser::visit处理:

template <typename Parser, typename V>
constexpr bool parse(V &input, const Parser& parser) noexcept {
    return parser.visit(input);
}

这种方式最简洁,只要Parser的visit方法能处理V类型(比如std::string_view和Span<Token>都实现了对应的接口),就能自动推导模板参数。

方案3:为输入类型提供统一的类型萃取

如果需要明确获取输入类型的value_type,可以定义一个类型萃取模板:

// 通用萃取模板
template<typename V>
struct viewer_value_type {
    using type = typename V::value_type;
};

// 为std::basic_string_view特化(可选,因为它本身已有value_type)
template<typename CharT>
struct viewer_value_type<std::basic_string_view<CharT>> {
    using type = CharT;
};

// 别名模板简化使用
template<typename V>
using viewer_value_type_t = typename viewer_value_type<V>::type;

修改parse函数,使用萃取的类型:

template <typename Parser, typename V>
constexpr bool parse(V &input, const Parser& parser) noexcept {
    // 若需要使用T,可通过viewer_value_type_t<V>获取
    using T = viewer_value_type_t<V>;
    return parser.visit(input);
}

此方案同样支持自动推导模板参数,无需显式指定。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 12:40:21