如何创建一个将其参数转发给 fmt::format 保持类型安全的函数?

Dan*_*iel 4 c++ c++20 fmt

我有两个广泛相关的问题。

我想创建一个函数,将参数转发到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;
}
Run Code Online (Sandbox Code Playgroud)

用 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
Run Code Online (Sandbox Code Playgroud)

并使用 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
Run Code Online (Sandbox Code Playgroud)

在库(core.h)中,它声明如下:

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
Run Code Online (Sandbox Code Playgroud)

问题是cppreference指示第一个参数的类型未指定。所以

  • 我怎样才能让这样的函数my_print传递参数fmt::format并仍然捕获相同类型的错误?对于任何类型的功能,是否有更通用的方法来执行此操作?
  • 如何推断函数的参数类型,例如std::format

对于更多上下文,我想创建一个std::format有条件调用的函数,如果不需要字符串,则完全避免格式化。如果你知道一个更好的方法来发表评论,我会非常感激。但是,我关于如何解决一般问题的问题仍然存在。

Jos*_*son 15

这是对构造函数的调用fmt::format_string需要是常量表达式,因此您的函数应该采用格式字符串作为 afmt::format_string而不是泛型类型:

template <typename... Args>
std::string my_print(fmt::format_string<Args...> s, Args&&... args)
{
    return fmt::format(s, std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)


N. *_*ead 5

您可以使用std::vformat/fmt::vformat代替。

template <typename... Args>
constexpr auto my_print(std::string_view fmt, Args&&... args) {
    return fmt::vformat(fmt, fmt::make_format_args(std::forward<Args>(args)...));
}
Run Code Online (Sandbox Code Playgroud)

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>
constexpr auto my_print(Args&&... args) {
    return fmt::format(fmt.str, std::forward<Args>(args)...);
}

// used like

my_print<"string: {}">(42);
Run Code Online (Sandbox Code Playgroud)

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>
constexpr auto my_print(F, Args&&... args) {
    return fmt::format(F::string, std::forward<Args>(args)...);
}

// used like

my_print("string: {}"_fmt, 42);
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/z/dx1TGdcM9

  • @康桓伟顺便说一句,`std::format`等是有效的。正如我在答案中指出的那样,最好在编译时捕获格式字符串的错误。如果您想了解更多详细信息,[P22163](http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2216r3.html) 是进行此更改的论文。 (2认同)
  • @JosephThomson 有关于此主题的 https://wg21.link/P2508 (2认同)