I'm writing a compile-time parser PoC (compiling a literal string to std::tuple
) based on C++20 (or 23 if necessary). Currently gcc, clang, and MSVC all crash for this code, and MSVC gives useful information -- compiler is out of heap space
.
#include <string>
#include <string_view>
#include <cstddef>
#include <tuple>
#include <utility>
consteval decltype(auto) tuple_push(auto t, auto e) {
return std::tuple_cat(t, std::make_tuple(e));
}
consteval decltype(auto) pattern_munch(auto t, std::string_view remaining) {
if (remaining.empty()) {
return t;
}
switch (remaining.front()) {
case 'f':
return pattern_munch(tuple_push(t, 1.f), remaining.substr(1));
case 'i':
return pattern_munch(tuple_push(t, 1), remaining.substr(1));
default:
throw "unhandled";
}
}
consteval decltype(auto) compile_pattern(std::string_view pattern) {
return pattern_munch(std::make_tuple(), pattern);
}
int main() {
constexpr auto result = compile_pattern("fif");
static_assert(std::is_same_v<decltype(result), std::tuple<float, int, float>>);
}
Since all functions are consteval
, and therefore the arguments should be compile-time knowable, why does the compiler seem to recurse forever? How do I fix/workaround the code?
Since all functions are
consteval
, and therefore the arguments should be compile-time knowable.
Even if consteval
function, parameters are not contant expression.
I think that the rational is that return type cannot depend of value of parameter.
How do I fix/workaround the code?
You might use char_sequence
instead of string_view
template <char c>
consteval decltype(auto) pattern_munch() {
if constexpr (c == 'f') { return 1.f; }
else if constexpr (c == 'i') { return 1; }
else { static_assert(false); }
}
template <char... Cs>
consteval decltype(auto) compile_pattern(char_sequence<Cs...>) {
return std::tuple(pattern_munch<Cs>()...);
}
Now, to construct the char_sequence
, you might use clang/gcc extension
// That template uses the extension
template<typename Char, Char... Cs>
constexpr auto operator"" _cs() -> std::integer_sequence<Char, Cs...> {
return {};
}
See my answer from String-interning at compiletime for profiling to have MAKE_STRING
macro if you cannot used the extension (Really more verbose, and hard coded limit for accepted string length).
and then
constexpr auto result_extension = compile_pattern("fif"_cs);
constexpr auto result_macro = compile_pattern(MAKE_STRING("fif"));
or, since C++20, use a literal class type as template parameter:
template <std::size_t N>
struct LiteralString
{
consteval LiteralString(const char (&s)[N]) { std::copy(s, s + N, &data[0]); }
static constexpr std::size_t size = N;
std::array<char, N> data{};
};
template <auto S>
constexpr auto compile_pattern()
{
return []<std::size_t...Is>(std::index_sequence<Is...>) {
return std::tuple(pattern_munch<S.data[Is]>()...);
}(std::make_index_sequence<std::size(S.data) - 1>());
}
/*constexpr*/ auto t = compile_pattern<LiteralString("fif")>();