This is a reduced example what I try to achieve for a compile-time parser in C++26 (I know, not released, but that’s the option I give to gcc) and GCC 14.2.
#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.0n"sv).value;
std::cout << value;
return 0;
}
Error message:
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.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__))
| ^~~~~~~~~~~~~~~~~~~~~~~~
This example is not working, but the one with the inner for-loop only having the content of it used together with a fixed line at index 0 works.
Interestingly it works when I remove the n
from the constant.
The question is why and how can I solve that?
5
The problem has nothing to do with fancy C++26 stuff; it is a simple (but easy to miss) bug. If you end with n
and split on n
you will get an empty line. That empty line will contain no tokens. And empty vectors don’t have a ‘0th’ element.
To fix this you can add std::views::filter([](auto&& x){return !x.empty();}) |
in the first transformation sequence before the transformation to std::vector
to filter out empty lines, as they don’t have any tokens.
Edit: I also recommend filtering out empty tokens in the same way in the loop.
2