The following code snippet fails to compile on the latest version of MSVC (Visual Studio 2022 17.2.2). The same snippet seemed to work just fine on previous compiler versions.
#include <iostream>
#include <format>
template <typename First, typename... Args>
inline auto format1(First&& first, Args&&... args) -> decltype(std::format(first, std::forward<Args>(args)...))
{
return std::format(std::forward<First>(first), std::forward<Args>(args)...);
}
int main()
{
std::cout << format1("hi {} {}", 123, 456);
}
The compiler emits the following error:
1>ConsoleApplication3.cpp(10,24): message : failure was caused by a read of a variable outside its lifetime 1>ConsoleApplication3.cpp(10,24): message : see usage of 'first' 1>ConsoleApplication3.cpp(14): message : see reference to function template instantiation 'std::string format<const char(&)[9],int,int>(First,int &&,int &&)' being compiled 1>
with 1> [ 1> First=const char (&)[9] 1> ]
It seems that somehow forwarding a string literal to std::format makes the compiler think that they are used outside of their lifetime. I tried changing the function to accept const First& first
and all sorts of other variants but the error remains.
As far as I understand, when First
is deduced to a const reference, its lifetime should be extended to the scope of the invoked function.
So why do I get this error? How can I fix this?
Further investigating this, it seems like something specific to the use of std::format.
This snippet works fine when provided with a string literal:
template <std::size_t COUNT>
inline auto format2(const char (&first)[COUNT])
{
std::cout << first;
}
Wheras this one doesn't:
template <std::size_t COUNT>
inline auto format2(const char (&first)[COUNT])
{
std::format(first);
}
CodePudding user response:
After P2216, std::format
requires that the format string must be a core constant expression. In your case, the compilation fails because the function argument First
is not a constant expression.
The workaround is to use std::vformat
, which works for runtime format strings
template<typename First, typename... Args>
auto format1(First&& first, Args&&... args) {
return std::vformat(
std::forward<First>(first),
std::make_format_args(std::forward<Args>(args)...));
}
If you really want to use std::format
, you can pass in a lambda that returns a string literal
template<typename First, typename... Args>
auto format1(First first, Args&&... args) {
return std::format(first(), std::forward<Args>(args)...);
}
int main() {
std::cout << format1([]{ return "hi {} {}"; }, 123, 456);
}