我对为什么这段代码突然停止编译感到有些困惑:https://godbolt.org/z/hhM5GG78x
但是如果我将编译器改回 v19.31,它将编译:https://godbolt.org/z/11j8WbEzG
这是有问题的代码:
#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;
}
这是我收到的错误消息:
<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
它抱怨 fmt_str 在它的生命周期之外被使用。我没有看到这是怎么回事?
回答1
std::format
的第一个参数必须在编译时知道,因为 format 字符串被指定为只能构造为常量表达式。目的是保证无效 format 字符串的编译时错误。
fmt_str
是一个函数参数,因此它的 value 绝不是编译时常量。
您可以改用 https://en.cppreference.com/w/cpp/utility/format/vformat ,但它不会对 format 字符串执行编译时检查,而是将其延迟到运行时(出错时抛出 std::format_error
):
puts(std::vformat(fmt_str, std::make_format_args(args...)).c_str());
如果您不需要 fmt_str
依赖于运行时,则可以将其作为模板参数传递。不幸的是,目前这并不是那么直接,因为 std::string
和 std::string_view
不能用于此,并且字符串文字不能直接通过 const char*
非类型模板参数传递。
因此,您可能希望创建自己的结构固定长度字符串类型,可用作非类型模板参数,例如这里是用例的一个非常小的版本,您可能希望根据需要对其进行扩展:
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;
}
我假设 MSVC 根本还没有实现 std::format
的第一个参数在以前的版本中可以构造为常量表达式的要求。
该要求是在 C++20 之后为 C++23 添加的,但如果理解正确,也可以追溯适用于 C++20 作为缺陷报告,通过 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2216r3.html 论文中列出的投票包含相关更改。