How do I check at compile-time if there’s enough `{}` placeholders for all arguments?

std::format has a (compile-time and runtime) format string validation, but one thing not included in this validation is whether there are enough {} placeholders for all arguments (excessive arguments are silently ignored).

I assume this could be useful in some rare cases (e.g if you generate the format string), but I don’t need it, and I’ve had a few bugs because of this.

Is there anything I can do to check for this compile-time? Possibly by wrapping std::format in my own function.

Example:

#include <format>
#include <iostream>

int main()
{
    std::cout << std::format("{} {}", 1, 2, 3, 4, 5) << '\n'; // Prints `1 2`, no error!
}

NOTE: std::format does have compile-time format string validation. They just don’t check for this specific issue (excessive arguments).

  • For std::format you would have to provide everything at compile time so that checking is allready there.

    – 




  • @PepijnKramer This specific thing is not included in the validation, see example: gcc.godbolt.org/z/1q1fGc4Ph

    – 

  • 5

    @Someprogrammerdude std::format already checks the format string at compile-time using C++20 consteval magic. It’s just that this specific problem isn’t checked. While the compilers might add a warning for this eventually, it should also be possible to do this check from code alone (probably by wrapping std::format_string<...> in your own class).

    – 




  • 2

  • 1

    Tried a few ways but it seems that the issue is std::format is not failing in a SFINAE-friendly way.

    – 

You might indeed add wrappers:

template <typename... Ts>
struct my_format_string
{
    consteval my_format_string(const char* s) :
        my_format_string(std::format_string<Ts...>(s))
    {}

    consteval my_format_string(const std::format_string<Ts...> s) : s(s)
    {
        // Use proper validation check

        // placeholder validation of the placeholders
        if (std::count(s.get().begin(), s.get().end(), '{') != sizeof...(Ts)) {
            throw 42;
        }
    }
    constexpr auto get() const { return s; }

    std::format_string<Ts...> s;
};


template <typename... Ts>
auto my_format(my_format_string<std::type_identity_t<Ts>...> format, Ts... args)
{
    return std::vformat(format.getS().get(), std::make_format_args(args...));
}

Demo

Leave a Comment