{fmt}:始终在函数中编译时间检查格式字符串



我正在尝试创建一个自定义错误类,其构造函数通过向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()的情况下使此始终在编译时检查格式字符串?

您可以在C++20和{fmt}的最新版本中执行这样的编译时检查,如下所示:

#include <fmt/core.h>
class Err : public std::exception {
private:
std::string m_text;
public: 
template <typename... T>
Err(fmt::format_string<T...> format_str, T&&... args) {
m_text = fmt::format(format_str, std::forward<T>(args)...);
}
virtual const char* what() const noexcept { return m_text.c_str(); }
};
// ------------------------ 
throw Err("Error {:d}", 10);     // works
throw Err("Error {:d}", "abc");  // compile-time error

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

我今天也遇到了同样的问题,但我认为{fmt}库从那以后有所改进,所以这里是我的解决方案。

这里的想法是在异常构造函数中使用与fmt::format调用完全相同的调用:fmt:;format_string对象的构造,使用C++20 consteval,将在编译时解析和检查格式字符串。然后,格式字符串和可变参数被传递给vformat,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...));
}

这是为了确保在只有一个参数时始终选择另一个过载。

最新更新