【问题标题】:{fmt}: always compile-time check format string in function{fmt}:总是在函数中编译时检查格式字符串
【发布时间】:2021-07-08 14:10:05
【问题描述】:

我正在尝试创建一个自定义错误类,其构造函数通过将参数传递给fmt::format() 来创建错误消息。我希望它始终在编译时根据参数检查格式字符串,而不必每次抛出时都显式使用FMT_STRING()。比如:

class Err : public std::exception 
{
private:
    std::string m_text;
public: 
    template <typename S, typename... Args>
    Err(const S& format, Args&&... args) {
        m_text = fmt::format(FMT_STRING(format), args...);
    }
    
    virtual const char* what() const noexcept {return m_text.c_str();}
};

// ------------------------ 

throw Err("Error {:d}", 10);     // works
throw Err("Error {:d}", "abc");  // cause Compile-time error

使用前面的代码,我在 FMT_STRING() 宏上遇到错误:

error C2326: 'Err::{ctor}::<lambda_1>::()::FMT_COMPILE_STRING::operator fmt::v7::basic_string_view<char>(void) const': function cannot access 'format' 
message : see reference to function template instantiation 'Err::Err<char[11],int>(const S (&),int &&)' being compiled with [ S=char [11] ]

我对模板编程的经验很少。如何让它始终在编译时检查格式字符串,而无需每次都显式使用FMT_STRING()

【问题讨论】:

  • comile-time 检查需要FMT_STRING 是有原因的。如果字符串作为常规参数传递,则可能是不可能的。

标签: c++ constexpr compile-time fmt


【解决方案1】:

我今天遇到了同样的问题,但我认为 {fmt} 库已经改进了,所以这是我的解决方案。

这里的想法是在异常构造函数中调用与 fmt::format 调用完全相同的调用:使用 C++20 consteval 构造 fmt::format_string 对象将解析并检查格式字符串编译时间。然后,将格式字符串和可变参数传递给 vformat,这才是真正的工作。

#include <fmt/format.h>
#include <exception>
#include <iostream>

class Err : public std::exception 
{
private:
    std::string m_text;
public: 
    template <typename... Args>
    Err(fmt::format_string<Args...> fmt, Args&&... args) {
        m_text = fmt::vformat(fmt, fmt::make_format_args(args...));
    }
    
    virtual const char* what() const noexcept {return m_text.c_str();}
};

int main() {
    try {
        throw Err("Error {:d}", 10); // Works
        throw Err("Error {:d}", "abc"); // Compile error
    }
    catch(const std::exception& e){
        std::cout << e.what() << std::endl;
    }
}

在我的版本中,我最终为构造函数提供了一个覆盖,该构造函数采用单个字符串并完全跳过格式化,并将格式化构造函数更改为:

Err(fmt::format_string<T, Args...> fmt, T&& p1, Args&&... args) {
    m_text = fmt::vformat(fmt, fmt::make_format_args(p1, args...));
}

这是为了确保在只有一个参数时总是选择另一个重载。

【讨论】:

    【解决方案2】:

    您只能使用this github issue 中说明的方法在 C++20 中执行此类编译时检查:

    #include <fmt/format.h>
    
    template <typename... Args>
    struct format_str {
      fmt::string_view str;
    
      template <size_t N>
      consteval format_str(const char (&s)[N]) : str(s) {
        using checker = fmt::detail::format_string_checker<
          char, fmt::detail::error_handler, Args...>;
        fmt::detail::parse_format_string<true>(
          fmt::string_view(s, N), checker(s, {}));
      }
    };
    
    template <class... Args>
    std::string format(
        format_str<std::type_identity_t<Args>...> fmt,
        const Args&... args) {
      return fmt::format(fmt.str, args...);
    }
    
    int main() {
      auto s = format("{:d}", 42);
      fmt::print("{}\n", s);
    }
    

    Godbolt 演示:https://godbolt.org/z/qrh3ee

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-09-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多