Home > Software design >  Why passing a string literal to a template calling std::format fails to compile?
Why passing a string literal to a template calling std::format fails to compile?

Time:05-27

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

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

  • Related