c++templatesmetaprogrammingcompile-time

How to validate strings to compile time?


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 ss 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


Solution

  • 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 the i and ss 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);
    }