Search code examples
c++templatesc++20rvalue-reference

Why passing a string literal to a template calling std::format fails to compile?


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

Solution

  • 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