fmt/test/std-test.cc
nikita b059132e13 Support width and alignment in std::exception formatter
The formatter for std::exception (and std::exception_ptr) previously
accepted only the optional 't' type-name specifier, so standard fill,
align and width specifiers were rejected with "unknown format
specifier". This made it impossible to pad or align exception messages,
e.g. when laying them out in a log column.

Parse the standard fill/align/width specifiers (as the std::filesystem::
path formatter already does) and apply them when writing the message.
Dynamic width ({:{}}) and the 'none'/'unknown exception' cases for
exception_ptr are handled too. Existing behavior ({}, {:t}, nested
exception unwinding) is unchanged.
2026-07-20 17:29:16 +03:00

645 lines
22 KiB
C++

// Formatting library for C++ - tests of formatters for standard library types
//
// Copyright (c) 2012 - present, Victor Zverovich and {fmt} contributors
// All rights reserved.
//
// For the license information refer to format.h.
#include "fmt/std.h"
#include <bitset>
#include <stdexcept>
#include <string>
#include <vector>
#include "fmt/os.h" // fmt::system_category
#include "fmt/ranges.h"
#include "gtest-extra.h" // StartsWith
#ifdef __cpp_lib_filesystem
TEST(std_test, path) {
using std::filesystem::path;
EXPECT_EQ(fmt::format("{}", path("/usr/bin")), "/usr/bin");
// see #4303
const path p = "/usr/bin";
EXPECT_EQ(fmt::format("{}", p), "/usr/bin");
EXPECT_EQ(fmt::format("{:?}", path("/usr/bin")), "\"/usr/bin\"");
EXPECT_EQ(fmt::format("{:8}", path("foo")), "foo ");
EXPECT_EQ(fmt::format("{}", path("foo\"bar")), "foo\"bar");
EXPECT_EQ(fmt::format("{:?}", path("foo\"bar")), "\"foo\\\"bar\"");
EXPECT_EQ(fmt::format("{:g}", path("/usr/bin")), "/usr/bin");
# ifdef _WIN32
EXPECT_EQ(fmt::format("{}", path("C:\\foo")), "C:\\foo");
EXPECT_EQ(fmt::format("{:g}", path("C:\\foo")), "C:/foo");
EXPECT_EQ(fmt::format("{}", path(L"\x0428\x0447\x0443\x0447\x044B\x043D\x0448"
L"\x0447\x044B\x043D\x0430")),
"Шчучыншчына");
EXPECT_EQ(fmt::format("{}", path(L"\xD800")), "\xED\xA0\x80");
EXPECT_EQ(fmt::format("{}", path(L"[\xD800]")), "[\xED\xA0\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xDE00]")), "[\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{}", path(L"[\xD83D\xD83D\xDE00]")),
"[\xED\xA0\xBD\xF0\x9F\x98\x80]");
EXPECT_EQ(fmt::format("{:?}", path(L"\xD800")), "\"\\ud800\"");
# endif
}
// Intentionally delayed include to test #4303
# include "fmt/ranges.h"
// Test ambiguity problem described in #2954.
TEST(ranges_std_test, format_vector_path) {
auto p = std::filesystem::path("foo/bar.txt");
auto c = std::vector<std::string>{"abc", "def"};
EXPECT_EQ(fmt::format("path={}, range={}", p, c),
"path=foo/bar.txt, range=[\"abc\", \"def\"]");
}
// Test that path is not escaped twice in the debug mode.
TEST(ranges_std_test, format_quote_path) {
auto vec =
std::vector<std::filesystem::path>{"path1/file1.txt", "path2/file2.txt"};
EXPECT_EQ(fmt::format("{}", vec),
"[\"path1/file1.txt\", \"path2/file2.txt\"]");
# ifdef __cpp_lib_optional
auto o = std::optional<std::filesystem::path>("path/file.txt");
EXPECT_EQ(fmt::format("{}", o), "optional(\"path/file.txt\")");
EXPECT_EQ(fmt::format("{:?}", o), "optional(\"path/file.txt\")");
# endif
}
#endif
TEST(std_test, thread_id) {
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
}
TEST(std_test, complex) {
using limits = std::numeric_limits<double>;
EXPECT_EQ(fmt::format("{}", std::complex<double>(1, limits::quiet_NaN())),
"(1+nan i)");
EXPECT_EQ(fmt::format("{}", std::complex<double>(1, -limits::infinity())),
"(1-inf i)");
EXPECT_EQ(fmt::format("{}", std::complex<int>(1, 2)), "(1+2i)");
EXPECT_EQ(fmt::format("{}", std::complex<double>(1, 2.2)), "(1+2.2i)");
EXPECT_EQ(fmt::format("{}", std::complex<double>(1, -2.2)), "(1-2.2i)");
EXPECT_EQ(fmt::format("{}", std::complex<double>(0, 2.2)), "2.2i");
EXPECT_EQ(fmt::format("{}", std::complex<double>(0, -2.2)), "-2.2i");
EXPECT_EQ(fmt::format("{:+}", std::complex<double>(0, 2.2)), "+2.2i");
EXPECT_EQ(fmt::format("{:+}", std::complex<double>(0, -2.2)), "-2.2i");
EXPECT_EQ(fmt::format("{:+}", std::complex<double>(1, -2.2)), "(+1-2.2i)");
EXPECT_EQ(fmt::format("{:+}", std::complex<double>(1, 2.2)), "(+1+2.2i)");
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, 2.2)), "( 1+2.2i)");
EXPECT_EQ(fmt::format("{: }", std::complex<double>(1, -2.2)), "( 1-2.2i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), "(1+2i) ");
EXPECT_EQ(fmt::format("{:-<8}", std::complex<double>(1, 2)), "(1+2i)--");
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
" (1.00+2.20i)");
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, 2.2)),
"(1.00+2.20i) ");
EXPECT_EQ(fmt::format("{:<20.2f}", std::complex<double>(1, -2.2)),
"(1.00-2.20i) ");
EXPECT_EQ(fmt::format("{:<{}.{}f}", std::complex<double>(1, -2.2), 20, 2),
"(1.00-2.20i) ");
}
#ifdef __cpp_lib_source_location
TEST(std_test, source_location) {
std::source_location loc = std::source_location::current();
EXPECT_EQ(fmt::format("{}", loc),
fmt::format("{}:{}:{}: {}", loc.file_name(), loc.line(),
loc.column(), loc.function_name()));
}
#endif
TEST(std_test, optional) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<int>{}), "none");
EXPECT_EQ(fmt::format("{}", std::pair{1, "second"}), "(1, \"second\")");
EXPECT_EQ(fmt::format("{}", std::vector{std::optional{1}, std::optional{2},
std::optional{3}}),
"[optional(1), optional(2), optional(3)]");
EXPECT_EQ(
fmt::format("{}", std::optional<std::optional<const char*>>{{"nested"}}),
"optional(optional(\"nested\"))");
EXPECT_EQ(
fmt::format("{:<{}}", std::optional{std::string{"left aligned"}}, 30),
"optional(\"left aligned\" )");
EXPECT_EQ(
fmt::format("{::d}", std::optional{std::vector{'h', 'e', 'l', 'l', 'o'}}),
"optional([104, 101, 108, 108, 111])");
EXPECT_EQ(fmt::format("{}", std::optional{std::string{"string"}}),
"optional(\"string\")");
EXPECT_EQ(fmt::format("{}", std::optional{'C'}), "optional(\'C\')");
EXPECT_EQ(fmt::format("{:.{}f}", std::optional{3.14}, 1), "optional(3.1)");
struct unformattable {};
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::optional<unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::optional<const int>>::value));
#endif
}
TEST(std_test, expected) {
#ifdef __cpp_lib_expected
EXPECT_EQ(fmt::format("{}", std::expected<void, int>{}), "expected()");
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{1}), "expected(1)");
EXPECT_EQ(fmt::format("{}", std::expected<int, int>{std::unexpected(1)}),
"unexpected(1)");
EXPECT_EQ(fmt::format("{}", std::expected<std::string, int>{"test"}),
"expected(\"test\")");
EXPECT_EQ(fmt::format(
"{}", std::expected<int, std::string>{std::unexpected("test")}),
"unexpected(\"test\")");
EXPECT_EQ(fmt::format("{}", std::expected<char, int>{'a'}), "expected('a')");
EXPECT_EQ(fmt::format("{}", std::expected<int, char>{std::unexpected('a')}),
"unexpected('a')");
struct unformattable1 {};
struct unformattable2 {};
EXPECT_FALSE((fmt::is_formattable<unformattable1>::value));
EXPECT_FALSE((fmt::is_formattable<unformattable2>::value));
EXPECT_FALSE((fmt::is_formattable<
std::expected<unformattable1, unformattable2>>::value));
EXPECT_FALSE(
(fmt::is_formattable<std::expected<unformattable1, int>>::value));
EXPECT_FALSE(
(fmt::is_formattable<std::expected<int, unformattable2>>::value));
EXPECT_TRUE((fmt::is_formattable<std::expected<int, int>>::value));
EXPECT_TRUE((fmt::is_formattable<std::expected<void, int>>::value));
EXPECT_EQ(fmt::format("{}", std::unexpected{1}), "unexpected(1)");
EXPECT_EQ(fmt::format("{}", std::unexpected<std::string>{"test"}),
"unexpected(\"test\")");
EXPECT_EQ(fmt::format("{}", std::unexpected<char>{'a'}), "unexpected('a')");
EXPECT_FALSE((fmt::is_formattable<std::unexpected<unformattable2>>::value));
#endif
}
namespace my_nso {
enum class my_number {
one,
two,
};
auto format_as(my_number number) -> fmt::string_view {
return number == my_number::one ? "first" : "second";
}
class my_class {
public:
int av;
private:
friend auto format_as(const my_class& elm) -> std::string {
return fmt::to_string(elm.av);
}
};
class my_class_int {
public:
int av;
private:
friend auto format_as(const my_class_int& elm) -> int { return elm.av; }
};
} // namespace my_nso
TEST(std_test, expected_format_as) {
#ifdef __cpp_lib_expected
EXPECT_EQ(
fmt::format(
"{}", std::expected<my_nso::my_number, int>{my_nso::my_number::one}),
"expected(\"first\")");
EXPECT_EQ(
fmt::format("{}",
std::expected<my_nso::my_class, int>{my_nso::my_class{7}}),
"expected(\"7\")");
EXPECT_EQ(fmt::format("{}",
std::expected<my_nso::my_class_int, int>{
my_nso::my_class_int{8}}),
"expected(8)");
#endif
}
TEST(std_test, optional_format_as) {
#ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_number>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_number::one}),
"optional(\"first\")");
EXPECT_EQ(fmt::format("{}", std::optional<my_nso::my_class>{}), "none");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class{7}}),
"optional(\"7\")");
EXPECT_EQ(fmt::format("{}", std::optional{my_nso::my_class_int{8}}),
"optional(8)");
#endif
}
struct throws_on_move {
throws_on_move() = default;
[[noreturn]] throws_on_move(throws_on_move&&) {
throw std::runtime_error("Thrown by throws_on_move");
}
throws_on_move(const throws_on_move&) = default;
};
namespace fmt {
template <> struct formatter<throws_on_move> : formatter<string_view> {
auto format(const throws_on_move&, format_context& ctx) const
-> decltype(ctx.out()) {
string_view str("<throws_on_move>");
return formatter<string_view>::format(str, ctx);
}
};
} // namespace fmt
TEST(std_test, variant) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::monostate{}), "monostate");
using V0 = std::variant<int, float, std::string, char>;
V0 v0(42);
V0 v1(1.5f);
V0 v2("hello");
V0 v3('i');
EXPECT_EQ(fmt::format("{}", v0), "variant(42)");
EXPECT_EQ(fmt::format("{}", v1), "variant(1.5)");
EXPECT_EQ(fmt::format("{}", v2), "variant(\"hello\")");
EXPECT_EQ(fmt::format("{}", v3), "variant('i')");
struct unformattable {};
EXPECT_FALSE((fmt::is_formattable<unformattable>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<unformattable>>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<unformattable, int>>::value));
EXPECT_FALSE((fmt::is_formattable<std::variant<int, unformattable>>::value));
EXPECT_FALSE(
(fmt::is_formattable<std::variant<unformattable, unformattable>>::value));
EXPECT_TRUE((fmt::is_formattable<std::variant<int, float>>::value));
using V1 = std::variant<std::monostate, std::string, std::string>;
V1 v4{};
V1 v5{std::in_place_index<1>, "yes, this is variant"};
EXPECT_EQ(fmt::format("{}", v4), "variant(monostate)");
EXPECT_EQ(fmt::format("{}", v5), "variant(\"yes, this is variant\")");
volatile int i = 42; // Test compile error before GCC 11 described in #3068.
EXPECT_EQ(fmt::format("{}", i), "42");
std::variant<std::monostate, throws_on_move> v6;
try {
throws_on_move thrower;
v6.emplace<throws_on_move>(std::move(thrower));
} catch (const std::runtime_error&) {
}
// v6 is now valueless by exception
EXPECT_EQ(fmt::format("{}", v6), "variant(valueless by exception)");
#endif
}
TEST(std_test, variant_format_as) {
#ifdef __cpp_lib_variant
EXPECT_EQ(fmt::format("{}", std::variant<my_nso::my_number>{}),
"variant(\"first\")");
EXPECT_EQ(fmt::format(
"{}", std::variant<my_nso::my_number>{my_nso::my_number::one}),
"variant(\"first\")");
EXPECT_EQ(
fmt::format("{}", std::variant<my_nso::my_class>{my_nso::my_class{7}}),
"variant(\"7\")");
EXPECT_EQ(
fmt::format("{}",
std::variant<my_nso::my_class_int>{my_nso::my_class_int{8}}),
"variant(8)");
#endif
}
TEST(std_test, error_code) {
auto& generic = std::generic_category();
EXPECT_EQ(fmt::format("{}", std::error_code(42, generic)), "generic:42");
EXPECT_EQ(fmt::format("{:>12}", std::error_code(42, generic)),
" generic:42");
EXPECT_EQ(fmt::format("{:12}", std::error_code(42, generic)), "generic:42 ");
EXPECT_EQ(fmt::format("{}", std::error_code(42, fmt::system_category())),
"system:42");
EXPECT_EQ(fmt::format("{}", std::error_code(-42, fmt::system_category())),
"system:-42");
auto ec = std::make_error_code(std::errc::value_too_large);
EXPECT_EQ(fmt::format("{:s}", ec), ec.message());
EXPECT_EQ(fmt::format("{:?}", std::error_code(42, generic)),
"\"generic:42\"");
EXPECT_EQ(fmt::format("{}",
std::map<std::error_code, int>{
{std::error_code(42, generic), 0}}),
"{\"generic:42\": 0}");
}
template <typename Catch> void exception_test() {
try {
throw std::runtime_error("Test Exception");
} catch (const Catch& ex) {
EXPECT_EQ("Test Exception", fmt::format("{}", ex));
EXPECT_EQ("std::runtime_error: Test Exception", fmt::format("{:t}", ex));
}
}
namespace my_ns1 {
namespace my_ns2 {
struct my_exception : public std::exception {
private:
std::string msg;
public:
my_exception(const std::string& s) : msg(s) {}
const char* what() const noexcept override;
};
const char* my_exception::what() const noexcept { return msg.c_str(); }
} // namespace my_ns2
} // namespace my_ns1
TEST(std_test, exception) {
using testing::StartsWith;
exception_test<std::exception>();
exception_test<std::runtime_error>();
try {
using namespace my_ns1::my_ns2;
throw my_exception("My Exception");
} catch (const std::exception& ex) {
EXPECT_EQ("my_ns1::my_ns2::my_exception: My Exception",
fmt::format("{:t}", ex));
EXPECT_EQ("My Exception", fmt::format("{:}", ex));
}
try {
throw std::system_error(std::error_code(), "message");
} catch (const std::system_error& ex) {
EXPECT_THAT(fmt::format("{:t}", ex), StartsWith("std::system_error: "));
}
#ifdef __cpp_lib_filesystem
// Tests that the inline namespace is stripped out, e.g.
// std::filesystem::__cxx11::* -> std::filesystem::*.
try {
throw std::filesystem::filesystem_error("message", std::error_code());
} catch (const std::filesystem::filesystem_error& ex) {
EXPECT_THAT(fmt::format("{:t}", ex),
StartsWith("std::filesystem::filesystem_error: "));
}
#endif
#if FMT_USE_RTTI
// Nested exceptions (e.g. from std::throw_with_nested) are unwound.
try {
try {
throw std::runtime_error("inner");
} catch (...) {
std::throw_with_nested(std::runtime_error("outer"));
}
} catch (const std::exception& ex) {
EXPECT_EQ("outer: inner", fmt::format("{}", ex));
}
// Multiple levels of nesting.
try {
try {
try {
throw std::runtime_error("level 3");
} catch (...) {
std::throw_with_nested(std::runtime_error("level 2"));
}
} catch (...) {
std::throw_with_nested(std::runtime_error("level 1"));
}
} catch (const std::exception& ex) {
EXPECT_EQ("level 1: level 2: level 3", fmt::format("{}", ex));
}
#endif // FMT_USE_RTTI
}
TEST(std_test, exception_ptr) {
std::exception_ptr p1 = nullptr;
std::exception_ptr p2;
try {
using namespace my_ns1::my_ns2;
throw my_exception("My Exception");
} catch (...) {
p2 = std::current_exception();
}
EXPECT_EQ(fmt::format("{}", p1), "none");
EXPECT_EQ(fmt::format("{}", p2), "My Exception");
#if FMT_USE_RTTI
EXPECT_EQ(fmt::format("{:t}", p2),
"my_ns1::my_ns2::my_exception: My Exception");
// Nested exceptions are unwound through an exception_ptr too.
std::exception_ptr p3;
try {
try {
throw std::runtime_error("inner");
} catch (...) {
std::throw_with_nested(std::runtime_error("outer"));
}
} catch (...) {
p3 = std::current_exception();
}
EXPECT_EQ(fmt::format("{}", p3), "outer: inner");
#endif // FMT_USE_RTTI
}
TEST(std_test, exception_align) {
// The exception formatter supports standard width, fill and alignment.
auto ex = std::runtime_error("boom");
EXPECT_EQ(fmt::format("{:>8}", ex), " boom");
EXPECT_EQ(fmt::format("{:<8}", ex), "boom ");
EXPECT_EQ(fmt::format("{:^8}", ex), " boom ");
EXPECT_EQ(fmt::format("{:*>8}", ex), "****boom");
EXPECT_EQ(fmt::format("{:{}}", ex, 8), "boom ");
// A width smaller than the message leaves the message untouched.
EXPECT_EQ(fmt::format("{:>2}", ex), "boom");
#if FMT_USE_RTTI
// Alignment combines with the 't' (type name) presentation type.
try {
using namespace my_ns1::my_ns2;
throw my_exception("oops");
} catch (const std::exception& e) {
auto base = fmt::format("{:t}", e);
auto padded = fmt::format("{:*<40t}", e);
EXPECT_EQ(padded.size(), 40u);
EXPECT_EQ(padded.substr(0, base.size()), base);
EXPECT_EQ(padded.substr(base.size()), std::string(40 - base.size(), '*'));
}
#endif // FMT_USE_RTTI
// exception_ptr honors the same specifiers, for both the empty ("none")
// and the non-empty case.
std::exception_ptr enull = nullptr;
EXPECT_EQ(fmt::format("{:>8}", enull), " none");
std::exception_ptr ep;
try {
throw std::runtime_error("bang");
} catch (...) {
ep = std::current_exception();
}
EXPECT_EQ(fmt::format("{:>8}", ep), " bang");
}
#if FMT_USE_RTTI
TEST(std_test, type_info) {
EXPECT_EQ(fmt::format("{}", typeid(std::runtime_error)),
"std::runtime_error");
}
#endif // FMT_USE_RTTI
#if FMT_USE_BITINT
FMT_PRAGMA_CLANG(diagnostic ignored "-Wbit-int-extension")
TEST(std_test, bitint) {
using fmt::detail::bitint;
using fmt::detail::ubitint;
EXPECT_EQ(fmt::format("{}", ubitint<3>(7)), "7");
EXPECT_EQ(fmt::format("{}", bitint<7>()), "0");
EXPECT_EQ(fmt::format("{}", ubitint<15>(31000)), "31000");
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MIN)), "-32768");
EXPECT_EQ(fmt::format("{}", bitint<16>(INT16_MAX)), "32767");
EXPECT_EQ(fmt::format("{}", ubitint<32>(4294967295)), "4294967295");
EXPECT_EQ(fmt::format("{}", ubitint<47>(140737488355327ULL)),
"140737488355327");
EXPECT_EQ(fmt::format("{}", bitint<47>(-40737488355327LL)),
"-40737488355327");
// Check lvalues and const
auto a = bitint<8>(0);
auto b = ubitint<32>(4294967295);
const auto c = bitint<7>(0);
const auto d = ubitint<32>(4294967295);
EXPECT_EQ(fmt::format("{}", a), "0");
EXPECT_EQ(fmt::format("{}", b), "4294967295");
EXPECT_EQ(fmt::format("{}", c), "0");
EXPECT_EQ(fmt::format("{}", d), "4294967295");
static_assert(fmt::is_formattable<bitint<64>, char>{}, "");
static_assert(fmt::is_formattable<ubitint<64>, char>{}, "");
}
#endif
TEST(std_test, format_bit_reference) {
std::bitset<2> bs(1);
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
std::vector<bool> v = {true, false};
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
}
TEST(std_test, format_const_bit_reference) {
const std::bitset<2> bs(1);
EXPECT_EQ(fmt::format("{} {}", bs[0], bs[1]), "true false");
const std::vector<bool> v = {true, false};
EXPECT_EQ(fmt::format("{} {}", v[0], v[1]), "true false");
}
TEST(std_test, format_bitset) {
auto bs = std::bitset<6>(42);
EXPECT_EQ(fmt::format("{}", bs), "101010");
EXPECT_EQ(fmt::format("{:0>8}", bs), "00101010");
EXPECT_EQ(fmt::format("{:-^12}", bs), "---101010---");
}
#ifdef __cpp_lib_byte
TEST(base_test, format_byte) {
auto s = std::string();
fmt::format_to(std::back_inserter(s), "{}", std::byte(42));
EXPECT_EQ(s, "42");
}
#endif
TEST(std_test, format_atomic) {
std::atomic<bool> b(false);
EXPECT_EQ(fmt::format("{}", b), "false");
const std::atomic<bool> cb(true);
EXPECT_EQ(fmt::format("{}", cb), "true");
}
#ifdef __cpp_lib_atomic_flag_test
TEST(std_test, format_atomic_flag) {
std::atomic_flag f;
(void)f.test_and_set();
EXPECT_EQ(fmt::format("{}", f), "true");
f.clear();
const std::atomic_flag& cf = f;
EXPECT_EQ(fmt::format("{}", cf), "false");
}
#endif // __cpp_lib_atomic_flag_test
TEST(std_test, format_unique_ptr) {
std::unique_ptr<int> up(new int(1));
EXPECT_EQ(fmt::format("{}", fmt::ptr(up.get())),
fmt::format("{}", fmt::ptr(up)));
struct custom_deleter {
void operator()(int* p) const { delete p; }
};
std::unique_ptr<int, custom_deleter> upcd(new int(1));
EXPECT_EQ(fmt::format("{}", fmt::ptr(upcd.get())),
fmt::format("{}", fmt::ptr(upcd)));
}
TEST(std_test, format_shared_ptr) {
std::shared_ptr<int> sp(new int(1));
EXPECT_EQ(fmt::format("{}", fmt::ptr(sp.get())),
fmt::format("{}", fmt::ptr(sp)));
}
TEST(std_test, format_reference_wrapper) {
int num = 35;
EXPECT_EQ(fmt::to_string(std::cref(num)), "35");
EXPECT_EQ(fmt::to_string(std::ref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::cref(num)), "35");
EXPECT_EQ(fmt::format("{}", std::ref(num)), "35");
}
// Regression test for https://github.com/fmtlib/fmt/issues/4424.
struct type_with_format_as {};
int format_as(type_with_format_as) { return 20; }
TEST(std_test, format_reference_wrapper_with_format_as) {
type_with_format_as t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "20");
EXPECT_EQ(fmt::to_string(std::ref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "20");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "20");
}
struct type_with_format_as_string {};
std::string format_as(type_with_format_as_string) { return "foo"; }
TEST(std_test, format_reference_wrapper_with_format_as_string) {
type_with_format_as_string t;
EXPECT_EQ(fmt::to_string(std::cref(t)), "foo");
EXPECT_EQ(fmt::to_string(std::ref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::cref(t)), "foo");
EXPECT_EQ(fmt::format("{}", std::ref(t)), "foo");
}