Reputation: 83
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?
Upvotes: 8
Views: 2158
Reputation: 76688
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.
Upvotes: 9