'Wrapping std::format in a template function fails to compile with the latest MSVC compiler update

I'm a bit stumped as to why this code has suddenly stopped compiling: https://godbolt.org/z/hhM5GG78x

But if I change the compiler back v19.31, it will compile: https://godbolt.org/z/11j8WbEzG

Here's the code in question:

#include <format>
#include <string>

template <typename... Args>
void FormatString(const std::string_view& fmt_str, Args&&... args)
{
    puts(std::format(fmt_str, args...).c_str());
}

int main()
{
    FormatString("This is a {}.\n", "test");
    return 0;
}

Here's the error message I'm receiving:

<source>(7): error C7595: 'std::_Basic_format_string<char,const char (&)[5]>::_Basic_format_string': call to immediate function is not a constant expression
<source>(7): note: failure was caused by a read of a variable outside its lifetime
<source>(7): note: see usage of 'fmt_str'
<source>(12): note: see reference to function template instantiation 'void FormatString<const char(&)[5]>(const std::string_view &,const char (&)[5])' being compiled

It's complaining about fmt_str being used outside of it's lifetime. I'm failing to see how this is the case?



Solution 1:[1]

The first argument to std::format must be known at compile-time, since the format string is specified to be only constructible as a constant expression. The purpose is to guarantee compile-time errors for invalid format strings.

fmt_str is a function parameter and so its value is never a compile-time constant.

You can use std::vformat instead, but it will perform no compile-time checks of the format string, instead delaying it to runtime (throwing std::format_error on error):

puts(std::vformat(fmt_str, std::make_format_args(args...)).c_str());

If you don't need fmt_str to be runtime-time-dependent, you can pass it as a template paramter instead. Unfortunately this isn't quite so straight-forward at the moment since std::string and std::string_view cannot be used for that and string literals can't be passed directly via const char* non-type template argument.

So you would probably want to create your own structural fixed-length string type that can be used as non-type template parameter, e.g. here a very minimal version for the use case, which you would probably want to extend as suits your needs:

template<std::size_t N>
struct fixed_string {
    char str[N];
    constexpr fixed_string(const char (&str_)[N]) noexcept {
        std::ranges::copy(str_, str);
    }
};

template <fixed_string fmt_str, typename... Args>
void FormatString(Args&&... args)
{
    puts(std::format(fmt_str.str, args...).c_str());
}

int main()
{
    FormatString<"This is a {}.\n">("test");
    return 0;
}

I would assume that MSVC simply hadn't yet implemented the requirement that the first parameter of std::format be constructible as a constant expresssion in the previous versions.

The requirement was added after C++20 for C++23, but if understand correctly applies retroactively to C++20 as defect report as well, going by the votes listed in the paper here which contains the relevant changes.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1