5

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);
}
Elad Maimoni
  • 3,703
  • 3
  • 20
  • 37
  • 2
    [Passing a string literal as a type argument to a class template](https://stackoverflow.com/questions/2033110/passing-a-string-literal-as-a-type-argument-to-a-class-template), ---> [C-Style Strings as template arguments?](https://stackoverflow.com/questions/1826464/c-style-strings-as-template-arguments) and [Trying to pass string literals as template arguments](https://stackoverflow.com/questions/18031818/trying-to-pass-string-literals-as-template-arguments) – Jason May 27 '22 at 09:48
  • 1
    These issues are unrelated. I believe this problem is more specific to std::format implementation. – Elad Maimoni May 27 '22 at 09:56
  • The issues _are_ related because you can reshape your function to receive the format as C-style string template argument and use it like: format2<"format{}">(2); or via a macro: #define FMT(fmt, ...) format2(__VA_ARGS__) – Solo Jun 08 '22 at 12:52

1 Answers1

9

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(args...));
}

Demo

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);
}

Demo

vitaut
  • 49,672
  • 25
  • 199
  • 336
康桓瑋
  • 33,481
  • 5
  • 40
  • 90
  • 1
    thanks, can you please explain why First is not a constant expression? can I somehow declare my template to correctly deduce a constant expression? – Elad Maimoni May 27 '22 at 09:58
  • Note that arguments to make_format_args shouldn't be forwarded, see e.g. eel.is/c++draft/format#functions-2 – vitaut May 30 '23 at 16:33