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

为何std::vector::at在C++26 constexpr范围for循环中失效?

GCC编译期解析器触发非constexpr函数调用错误

场景与代码

在GCC 14.2环境下尝试实现基于C++26(GCC可选特性)的编译期解析器,编写了如下简化代码:

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

using namespace std::string_view_literals;

template <class To>
inline constexpr auto static_caster = []<class From>(From &&from) -> To
    requires requires { static_cast<To>(std::declval<From>()); }
{ return static_cast<To>(std::forward<From>(from)); };

struct Test {
    bool value;
    constexpr Test(const std::string_view input) {
        const std::vector<std::string_view> lines =
            input | std::views::split('\n') |
            std::views::transform(static_caster<std::string_view>) |
            std::ranges::to<std::vector>();
        // const std::string_view line = lines.at(0);
        for (const std::string_view line : lines) {
            const std::vector<std::string_view> line_tokens =
                line | std::views::split(' ') |
                std::views::transform(static_caster<std::string_view>) |
                std::ranges::to<std::vector>();
            const std::string_view kind = line_tokens.at(0);
            this->value = "v" == kind;
        }
    }
};

int main(int argc, char *argv[]) {
    constexpr bool value = Test("v 1.0 2.0 3.0\n"sv).value;

    std::cout << value;

    return 0;
}

编译错误信息

编译时触发如下错误:

In file included from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/vector:68,
                 from <source>:4:
/opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/stl_vector.h: In function 'int main(int, char**)':
<source>:33:52:   in 'constexpr' expansion of 'Test(std::literals::string_view_literals::operator""sv(((const char*)"v 1.0 2.0 3.0\012"), 14))'
<source>:26:57:   in 'constexpr' expansion of 'line_tokens.std::vector<std::basic_string_view<char> >::at(0)'
/opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/stl_vector.h:1333:16:   in 'constexpr' expansion of '((const std::vector<std::basic_string_view<char> >*)this)->std::vector<std::basic_string_view<char> >::_M_range_check(__n)'
/opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/stl_vector.h:1292:35: error: call to non-'constexpr' function 'void std::__throw_out_of_range_fmt(const char*, ...)'
 1292 |           __throw_out_of_range_fmt(__N("vector::_M_range_check: __n "
      |           ~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1293 |                                        "(which is %zu) >= this->size() "
      |                                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1294 |                                        "(which is %zu)"),
      |                                        ~~~~~~~~~~~~~~~~~~
 1295 |                                    __n, this->size());
      |                                    ~~~~~~~~~~~~~~~~~~
In file included from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/new_allocator.h:35,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/x86_64-linux-gnu/bits/c++allocator.h:33,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/allocator.h:46,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/string:45,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/locale_classes.h:42,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/ios_base.h:43,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/ios:46,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/ostream:42,
                 from /opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/iostream:43,
                 from <source>:1:
/opt/compiler-explorer/gcc-trunk-20241219/include/c++/15.0.0/bits/functexcept.h:82:3: note: 'void std::__throw_out_of_range_fmt(const char*, ...)' declared here
   82 |   __throw_out_of_range_fmt(const char*, ...) __attribute__((__noreturn__,__cold__))
      |   ^~~~~~~~~~~~~~~~~~~~~~~~

已知两个现象:

  • 直接用lines.at(0)获取固定行而非遍历lines时,代码正常编译;
  • 移除输入常量中的\n也能正常编译。

问题原因

核心问题是输入字符串末尾的\n导致std::views::split生成了一个空的子范围:
当输入是"v 1.0 2.0 3.0\n"时,split('\n')会把字符串拆分成两个部分:第一部分是有效内容"v 1.0 2.0 3.0",第二部分是末尾\n之后的空字符串。

在编译期上下文中,constexpr代码的所有分支路径都必须是合法的——即使运行时不会走到错误分支,编译器也会检查所有可能的执行路径。当遍历lines时,第二个空字符串会进入循环:对空字符串执行split(' ')得到的是空的token列表,此时调用line_tokens.at(0)会触发越界检查,而GCC的std::vector::at在越界时调用的std::__throw_out_of_range_fmt并非constexpr函数,因此编译报错。

而直接取lines.at(0)时,编译器只会验证这一个路径的合法性(第一个元素是有效行,token列表非空);移除\n则不会生成空行,自然不会触发错误路径。


解决方法

方法1:过滤空行

在生成lines时,添加过滤空字符串的视图,跳过空行:

const std::vector<std::string_view> lines =
    input | std::views::split('\n') |
    std::views::transform(static_caster<std::string_view>) |
    std::views::filter([](std::string_view sv) { return !sv.empty(); }) | // 新增过滤逻辑
    std::ranges::to<std::vector>();

方法2:避免使用at(),改用operator[]配合前置检查

如果能确保token列表非空(比如过滤后),可以用operator[]代替at()——operator[]不做越界检查,也就不会调用抛出异常的非constexpr函数。或者在调用at()前先判断line_tokens的大小:

if (!line_tokens.empty()) {
    const std::string_view kind = line_tokens.at(0);
    this->value = "v" == kind;
}

方法3:处理末尾的分隔符

可以提前移除输入字符串末尾的\n,或者使用std::views::split的替代方式避免生成空范围。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 13:37:03