我想知道是否有一种方法可以让 gtest 理解用户定义类型 libfmt 的格式化程序,以便打印可读的错误输出? 我知道如何通过为这个用户定义类型添加流插入运算符
operator<<
来教 gtest 理解用户定义类型,例如
std::ostream& operator<<(std::ostream& stream, CpuTimes const& anything);
std::ostream& operator<<(std::ostream& stream, CpuStats const& anything);
这是有详细记录的here。
但我大量使用 libfmt,它需要实现格式化函数来生成用户定义类型的可读且可打印的输出。这个所谓的
fmt::formatter
实际上是一个模板特化,例如
namespace fmt {
template <> struct formatter<CpuTimes> : basics::fmt::ParseContextEmpty {
format_context::iterator format(CpuTimes const& times, format_context& ctx);
};
template <> struct formatter<CpuStats> : basics::fmt::ParseContextEmpty {
format_context::iterator format(CpuStats const& stats, format_context& ctx);
};
template <> struct formatter<CpuLimits> : basics::fmt::ParseContextEmpty {
format_context::iterator format(CpuLimits const& limits, format_context& ctx);
};
} // namespace fmt
为了让 gtest 理解这种格式,您必须为您希望 gtest 正确打印的每种类型一遍又一遍地为
operator<<
编写相同的实现,例如
std::ostream& operator<<(std::ostream& stream, CpuTimes const& anything) {
return stream << fmt::format("{}", anything);
}
std::ostream& operator<<(std::ostream& stream, CpuStats const& anything) {
return stream << fmt::format("{}", anything);
}
std::ostream& operator<<(std::ostream& stream, CpuLimits const& anything) {
return stream << fmt::format("{}", anything);
}
有没有办法让我不用写这些样板代码?
为了避免由于
PrintTo
或 operator<<
命名空间不明确而导致的问题,需要使用命名空间。
这是使用命名空间时的一些演示:
#include <fmt/format.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
namespace me {
struct Foo {
int x = 0;
double y = 0;
};
bool operator==(const Foo& a, const Foo& b)
{
return a.x == b.x && a.y == b.y;
}
}
template <>
struct fmt::formatter<me::Foo> {
char presentation = 'f';
constexpr auto parse(format_parse_context& ctx) -> decltype(ctx.begin())
{
auto it = ctx.begin(), end = ctx.end();
if (it != end && (*it == 'f' || *it == 'e'))
presentation = *it++;
if (it != end && *it != '}')
throw format_error("invalid format");
return it;
}
template <typename FormatContext>
auto format(const me::Foo& p, FormatContext& ctx) -> decltype(ctx.out())
{
return presentation == 'f'
? format_to(ctx.out(), "({}, {:.1f})", p.x, p.y)
: format_to(ctx.out(), "({}, {:.1e})", p.x, p.y);
}
};
#if VERSION == 1
namespace me {
template <typename T>
void PrintTo(const T& value, ::std::ostream* os)
{
*os << fmt::format(FMT_STRING("{}"), value);
}
}
#elif VERSION == 2
namespace me {
template <typename T>
std::ostream& operator<<(std::ostream& out, const T& value)
{
::std::operator<<(out, fmt::format(FMT_STRING("{}"), value));
return out;
}
}
#endif
class MagicTest : public testing::Test { };
TEST_F(MagicTest, CheckFmtFormater)
{
EXPECT_EQ(fmt::format("{}", me::Foo {}), "(0, 0.0)");
}
TEST_F(MagicTest, FailOnPurpuse)
{
EXPECT_EQ(me::Foo {}, (me::Foo { 1, 0 }));
}
删除
me
命名空间会导致所有实现存在歧义问题。
请注意,命名空间会导致使用依赖于参数的查找。
假设您已经经历了实例化
fmt::formatter
内容的麻烦,这是我的解决方案,使 MYSTUFF
命名空间中的所有内容能够自动获取 operator<<
我认为您正在要求:
namespace MYSTUFF {
template<typename T>
concept HasFormatter = fmt::is_formattable<T>::value;
template <HasFormatter T>
std::ostream& operator<<(std::ostream& out, const T& val)
{
::std::operator<<(out, fmt::format("{}", val));
return out;
}
} // MYSTUFF