I am trying to make the following code work:
#include <string>
#include <tuple>
#include <print>
template<char Specifier>
struct ParseSpecifier;
template<>
struct ParseSpecifier<'s'>
{
static void parse()
{
std::println("string");
}
};
template<>
struct ParseSpecifier<'i'>
{
static void parse()
{
std::println("integer");
}
};
template<std::size_t Index>
void parse_format(const std::string_view format)
{
if (Index == format.size())
return;
ParseSpecifier<format[Index]>::parse();
parse_format<Index + 1>(format);
}
int main()
{
parse_format<0>("iiisss");
}
I know there exist libraries (like fmt) that can compile time validate format strings so this must be possible somehow. I should note, the ultimate goal is not to print "string"
and "integer"
, but rather to create a std::tuple
based on the i
and s
s in the provided string. The string will always be known at compile time.
I am starting small as I am only just learning template metaprogramming. How can I make code like this work? I understand format is not constexpr
as function parameters cannot be. Additionally, std::string_view
cannot be a template parameter. If I use template<char... Chars>
then I have to call the function like parse_format<0, 'i', 's', 's', 'i', 's'>();
, which is horrible.
Is there any way I can get my attempted code to function? If not, what is the best strategy for compile time validation of strings?
Important note, the final function will be called with one compile time known parameter and one real time parameter (much like fmt::format
).
Easy edit: https://godbolt.org/z/s8eoYarja
The way libfmt (and std::format
) do it is by passing a custom class instead of std::string_view
, with a consteval
constructor accepting a string. Said constructor runs at compile-time and can e.g. throw (or do something else) to cause a compile-time error.
But this approach doesn't let you do this:
create a
std::tuple
based on thei
ands
s in the provided string
This requires passing the string as a template parameter:
template <std::size_t N>
struct ConstString
{
char value[N]{};
constexpr ConstString() {}
constexpr ConstString(const char (&str)[N])
{
std::copy_n(str, N, value);
}
[[nodiscard]] constexpr std::string_view view() const
{
return std::string_view(value, value + N - 1);
}
};
template <ConstString S>
void foo();
int main()
{
foo<"blah">();
}
Or with a slightly different call syntax:
template <ConstString>
struct ConstStringTag {};
template <ConstString S>
[[nodiscard]] ConstStringTag<S> constexpr operator""_c() {return {};}
template <ConstString S>
void foo(ConstStringTag<S>);
int main()
{
foo("blah"_c);
}