如何创建一个将其参数转发给 fmt::format 以保持类型安全的函数?
How to create a function that forwards its arguments to fmt::format keeping the type-safeness?
我有两个广泛相关的问题。
我想创建一个函数,将参数转发给 fmt::format
(然后在支持增加时转发给 std::format
)。像这样:
#include <iostream>
#include <fmt/core.h>
constexpr auto my_print(auto&& fmt, auto&&... args) {
// Error here!
// ~~~~~~~~v~~~~~~~~
return fmt::format(fmt, args...);
}
int main() {
std::cout << my_print("{}", 42) << std::endl;
}
使用 gcc 11.1.0 测试:
In instantiation of ‘constexpr auto my_print(auto:11&&, auto:12&& ...) [with auto:11 = const char (&)[3]; auto:12 = {int}]’:
error: ‘fmt’ is not a constant expression
并使用 clang 12.0.1 进行测试:
error: call to consteval function 'fmt::basic_format_string<char, int &>::basic_format_string<char [3], 0>' is not a constant expression
在库 (core.h) 中声明如下:
template <typename... T>
auto format(format_string<T...> fmt, T&&... args) -> std::string {
// ...
}
问题是cppreference表示第一个参数的类型未指定。所以
- 我怎样才能创建一个像
my_print
这样的函数,将参数传递给 fmt::format
并且仍然捕获相同类型的错误?对于任何类型的功能,是否有更通用的方法来执行此操作?
- 如何推断函数的参数类型,例如
std::format
?
对于更多上下文,我想创建一个有条件地调用 std::format
的函数,如果不需要字符串,则完全避免格式化。如果您知道更好的方法来发表评论,我将非常感激。但是,我关于如何解决一般问题的问题仍然存在。
C++23 可能包含 https://wg21.link/P2508R1,它将公开 std::format
使用的格式字符串类型。这对应于 libfmt 中提供的 fmt::format_string
类型。使用示例可能是:
template <typename... Args>
auto my_print(std::format_string<Args...> fmt, Args&&... args) {
return std::format(fmt, std::forward<Args>(args)...);
}
在 C++23 之前,您可以使用 std::vformat
/ fmt::vformat
代替。
template <typename... Args>
auto my_print(std::string_view fmt, Args&&... args) {
return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
}
https://godbolt.org/z/5YnY11vE4
问题是 std::format
(以及 fmt::format
的最新版本)需要第一个参数的常量表达式,正如您所注意到的。这样一来,如果格式字符串对于传入的参数没有意义,它可以提供编译时错误。使用 vformat
是解决这个问题的方法。
显然,这避开了通常对格式字符串进行的编译时检查:格式字符串的任何错误都将表现为运行时错误(异常)。
除了提供格式字符串作为模板参数外,我不确定是否有任何简单的方法可以避免这种情况。一次尝试可能是这样的:
template <std::size_t N>
struct static_string {
char str[N] {};
constexpr static_string(const char (&s)[N]) {
std::ranges::copy(s, str);
}
};
template <static_string fmt, typename... Args>
auto my_print(Args&&... args) {
return std::format(fmt.str, std::forward<Args>(args)...);
}
// used like
my_print<"string: {}">(42);
https://godbolt.org/z/5GW16Eac1
如果你真的想使用“normal-ish”语法传递参数,你可以使用用户定义的文字来构造一个在编译时存储字符串的类型:
template <std::size_t N>
struct static_string {
char str[N] {};
constexpr static_string(const char (&s)[N]) {
std::ranges::copy(s, str);
}
};
template <static_string s>
struct format_string {
static constexpr const char* string = s.str;
};
template <static_string s>
constexpr auto operator""_fmt() {
return format_string<s>{};
}
template <typename F, typename... Args>
auto my_print(F, Args&&... args) {
return std::format(F::string, std::forward<Args>(args)...);
}
// used like
my_print("string: {}"_fmt, 42);
调用fmt::format_string
的构造函数需要是常量表达式,所以你的函数应该将格式字符串作为fmt::format_string
而不是泛型:
template <typename... Args>
std::string my_print(fmt::format_string<Args...> s, Args&&... args)
{
return fmt::format(s, std::forward<Args>(args)...);
}
我有两个广泛相关的问题。
我想创建一个函数,将参数转发给 fmt::format
(然后在支持增加时转发给 std::format
)。像这样:
#include <iostream>
#include <fmt/core.h>
constexpr auto my_print(auto&& fmt, auto&&... args) {
// Error here!
// ~~~~~~~~v~~~~~~~~
return fmt::format(fmt, args...);
}
int main() {
std::cout << my_print("{}", 42) << std::endl;
}
使用 gcc 11.1.0 测试:
In instantiation of ‘constexpr auto my_print(auto:11&&, auto:12&& ...) [with auto:11 = const char (&)[3]; auto:12 = {int}]’:
error: ‘fmt’ is not a constant expression
并使用 clang 12.0.1 进行测试:
error: call to consteval function 'fmt::basic_format_string<char, int &>::basic_format_string<char [3], 0>' is not a constant expression
在库 (core.h) 中声明如下:
template <typename... T>
auto format(format_string<T...> fmt, T&&... args) -> std::string {
// ...
}
问题是cppreference表示第一个参数的类型未指定。所以
- 我怎样才能创建一个像
my_print
这样的函数,将参数传递给fmt::format
并且仍然捕获相同类型的错误?对于任何类型的功能,是否有更通用的方法来执行此操作? - 如何推断函数的参数类型,例如
std::format
?
对于更多上下文,我想创建一个有条件地调用 std::format
的函数,如果不需要字符串,则完全避免格式化。如果您知道更好的方法来发表评论,我将非常感激。但是,我关于如何解决一般问题的问题仍然存在。
C++23 可能包含 https://wg21.link/P2508R1,它将公开 std::format
使用的格式字符串类型。这对应于 libfmt 中提供的 fmt::format_string
类型。使用示例可能是:
template <typename... Args>
auto my_print(std::format_string<Args...> fmt, Args&&... args) {
return std::format(fmt, std::forward<Args>(args)...);
}
在 C++23 之前,您可以使用 std::vformat
/ fmt::vformat
代替。
template <typename... Args>
auto my_print(std::string_view fmt, Args&&... args) {
return std::vformat(fmt, std::make_format_args(std::forward<Args>(args)...));
}
https://godbolt.org/z/5YnY11vE4
问题是 std::format
(以及 fmt::format
的最新版本)需要第一个参数的常量表达式,正如您所注意到的。这样一来,如果格式字符串对于传入的参数没有意义,它可以提供编译时错误。使用 vformat
是解决这个问题的方法。
显然,这避开了通常对格式字符串进行的编译时检查:格式字符串的任何错误都将表现为运行时错误(异常)。
除了提供格式字符串作为模板参数外,我不确定是否有任何简单的方法可以避免这种情况。一次尝试可能是这样的:
template <std::size_t N>
struct static_string {
char str[N] {};
constexpr static_string(const char (&s)[N]) {
std::ranges::copy(s, str);
}
};
template <static_string fmt, typename... Args>
auto my_print(Args&&... args) {
return std::format(fmt.str, std::forward<Args>(args)...);
}
// used like
my_print<"string: {}">(42);
https://godbolt.org/z/5GW16Eac1
如果你真的想使用“normal-ish”语法传递参数,你可以使用用户定义的文字来构造一个在编译时存储字符串的类型:
template <std::size_t N>
struct static_string {
char str[N] {};
constexpr static_string(const char (&s)[N]) {
std::ranges::copy(s, str);
}
};
template <static_string s>
struct format_string {
static constexpr const char* string = s.str;
};
template <static_string s>
constexpr auto operator""_fmt() {
return format_string<s>{};
}
template <typename F, typename... Args>
auto my_print(F, Args&&... args) {
return std::format(F::string, std::forward<Args>(args)...);
}
// used like
my_print("string: {}"_fmt, 42);
调用fmt::format_string
的构造函数需要是常量表达式,所以你的函数应该将格式字符串作为fmt::format_string
而不是泛型:
template <typename... Args>
std::string my_print(fmt::format_string<Args...> s, Args&&... args)
{
return fmt::format(s, std::forward<Args>(args)...);
}