参见以下代码:
#include <print>
#include <exception>
#include <string>
#include <format>
class exception_t
: public std::exception
{
public:
template<class... args_t>
exception_t(std::string_view users_fmt, args_t&&... args)
: m_users_fmt(users_fmt)
, m_format_args(std::move(std::make_format_args(args...)))
{}
char const* what() const noexcept override
{
thread_local static std::string s = std::vformat(m_users_fmt, m_format_args);
return s.c_str();
}
private:
std::string m_users_fmt;
std::format_args m_format_args;
};
int main()
try
{
throw exception_t("{}", 42);
}
catch (std::exception& e)
{
std::println("{}", e.what());
}
在使用 SDK 10.0.19041.0 的 MSVC 2022 v143 下调试此问题时,我发现在构建 exception_t 期间,m_format_args 有一个适当的值,但在调用 What() 时,m_format_args 有一个无效值。这很可能是一生的问题,但我看不出出了什么问题。 (大多数情况下这个程序不打印 42)
我在这里做错了什么?
======================================================
HolyBlackCat回复后更新
上面是一个最小的代码片段,但其目的是将格式与异常/错误/警告报告完全分开。我想存储带有参数的标准类型,例如在磁盘上。随后另一个进程从磁盘读取数据并使用本地语言的格式字符串显示数据。这就是为什么我不想在此代码片段的构造函数中调用 std::vformat 的原因。
是否存在 std::format_args 的替代方案?例如。我可以存储参数吗?
我找到了一种存储参数并使程序运行的方法。我需要添加类型variant_t和variants_t并定义用户定义的格式化程序。那么上面的代码片段只需要一些小改动:
#include <print>
#include <exception>
#include <string>
#include <format>
class exception_t
: public std::exception
{
public:
template<class... args_t>
exception_t(std::string_view users_fmt, args_t&&... args)
: m_users_fmt(users_fmt)
, m_variants(args...)
{}
char const* what() const noexcept override
{
m_variants.resize(16, 0);
thread_local static std::string s = std::vformat(m_users_fmt, std::make_format_args(m_variants[0], m_variants[1], m_variants[2], m_variants[3], m_variants[4], m_variants[5], m_variants[6], m_variants[7], m_variants[8], m_variants[9], m_variants[10], m_variants[11], m_variants[12], m_variants[13], m_variants[14], m_variants[15]));
return s.c_str();
}
private:
std::string m_users_fmt;
mutable variants_t m_variants;
};
int main()
try
{
throw exception_t("{}", 42);
}
catch (std::exception& e)
{
std::println("{}", e.what());
}
这并不完全理想:
variant_t、variants_t 和用户定义格式化程序的定义如下:
#include <variant>
#include <vector>
using variant_t = std::variant
< bool
, char
, int
, unsigned int
, long long
, unsigned long long
, float
, double
, long double
, void const*
>;
// helper type for the visitor #4
template<class... Ts> struct overloaded : Ts... { using Ts::operator()...; };
template<>
struct std::formatter<variant_t, char>
{
template<class parse_context_t>
constexpr parse_context_t::iterator parse(parse_context_t& ctx)
{
for(auto it = ctx.begin(); it != ctx.end(); ++it)
{
m_format += *it;
if (*it == '}')
{
return it;
}
}
m_format += '}';
return ctx.end();
}
template<class fmt_context_t>
fmt_context_t::iterator format(variant_t const& variant, fmt_context_t& ctx) const
{
return std::visit(overloaded{
[&ctx, this](bool v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](char v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](int v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](unsigned int v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](long long v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](unsigned long long v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](float v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](double v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](long double v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
[&ctx, this](void const* v) { return std::vformat_to(ctx.out(), m_format, std::make_format_args(v)); },
}, variant);
}
std::string m_format{"{:"};
};
class variants_t
: public std::vector<variant_t>
{
public:
constexpr variants_t() = default;
template<class... args_t, class T>
constexpr variants_t(T first_arg, args_t&&... args)
{
push_back(first_arg);
variants_t v(args...);
insert(end(), v.begin(), v.end());
}
};