Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add std::complex formatter #3892

Merged
merged 3 commits into from
Mar 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 21 additions & 17 deletions include/fmt/format.h
Original file line number Diff line number Diff line change
Expand Up @@ -4136,33 +4136,37 @@ template <typename T> struct formatter<group_digits_view<T>> : formatter<T> {
}
};

template <typename T> struct nested_view {
const formatter<T>* fmt;
template <typename T, typename Char> struct nested_view {
const formatter<T, Char>* fmt;
const T* value;
};

template <typename T> struct formatter<nested_view<T>> {
FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> const char* {
template <typename T, typename Char>
struct formatter<nested_view<T, Char>, Char> {
template <typename ParseContext>
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
return ctx.begin();
}
auto format(nested_view<T> view, format_context& ctx) const
template <typename FormatContext>
auto format(nested_view<T, Char> view, FormatContext& ctx) const
-> decltype(ctx.out()) {
return view.fmt->format(*view.value, ctx);
}
};

template <typename T> struct nested_formatter {
template <typename T, typename Char = char> struct nested_formatter {
private:
int width_;
detail::fill_t fill_;
align_t align_ : 4;
formatter<T> formatter_;
formatter<T, Char> formatter_;

public:
constexpr nested_formatter() : width_(0), align_(align_t::none) {}

FMT_CONSTEXPR auto parse(format_parse_context& ctx) -> const char* {
auto specs = detail::dynamic_format_specs<char>();
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
-> decltype(ctx.begin()) {
auto specs = detail::dynamic_format_specs<Char>();
auto it = parse_format_specs(ctx.begin(), ctx.end(), specs, ctx,
detail::type::none_type);
width_ = specs.width;
Expand All @@ -4172,21 +4176,21 @@ template <typename T> struct nested_formatter {
return formatter_.parse(ctx);
}

template <typename F>
auto write_padded(format_context& ctx, F write) const -> decltype(ctx.out()) {
template <typename FormatContext, typename F>
auto write_padded(FormatContext& ctx, F write) const -> decltype(ctx.out()) {
if (width_ == 0) return write(ctx.out());
auto buf = memory_buffer();
write(appender(buf));
auto buf = basic_memory_buffer<Char>();
write(basic_appender<Char>(buf));
auto specs = format_specs();
specs.width = width_;
specs.fill = fill_;
specs.align = align_;
return detail::write<char>(ctx.out(), string_view(buf.data(), buf.size()),
specs);
return detail::write<Char>(
ctx.out(), basic_string_view<Char>(buf.data(), buf.size()), specs);
}

auto nested(const T& value) const -> nested_view<T> {
return nested_view<T>{&formatter_, &value};
auto nested(const T& value) const -> nested_view<T, Char> {
return nested_view<T, Char>{&formatter_, &value};
}
};

Expand Down
27 changes: 27 additions & 0 deletions include/fmt/std.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include <atomic>
#include <bitset>
#include <complex>
#include <cstdlib>
#include <exception>
#include <memory>
Expand Down Expand Up @@ -585,5 +586,31 @@ struct formatter<std::atomic_flag, Char> : formatter<bool, Char> {
};
#endif // __cpp_lib_atomic_flag_test

FMT_EXPORT
template <typename F, typename Char>
struct formatter<std::complex<F>, Char> : nested_formatter<F, Char> {
private:
// Functor because C++11 doesn't support generic lambdas.
struct writer {
const formatter<std::complex<F>, Char>* f;
const std::complex<F>& c;

template <typename OutputIt>
FMT_CONSTEXPR auto operator()(OutputIt out) -> OutputIt {
auto format =
detail::string_literal<Char, '(', '{', '}', ',', '{', '}', ')'>{};
return fmt::format_to(out, basic_string_view<Char>(format),
f->nested(c.real()), f->nested(c.imag()));
}
};

public:
template <typename FormatContext>
auto format(const std::complex<F>& c, FormatContext& ctx) const
-> decltype(ctx.out()) {
return this->write_padded(ctx, writer{this, c});
}
};

FMT_END_NAMESPACE
#endif // FMT_STD_H_
28 changes: 21 additions & 7 deletions test/std-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ TEST(std_test, thread_id) {
EXPECT_FALSE(fmt::format("{}", std::this_thread::get_id()).empty());
}

TEST(std_test, complex) {
EXPECT_EQ(fmt::format("{}", std::complex<double>(1, 2.2)), "(1,2.2)");
EXPECT_EQ(fmt::format("{:>20.2f}", std::complex<double>(1, 2.2)),
" (1.00,2.20)");
}

#ifdef __cpp_lib_source_location
TEST(std_test, source_location) {
std::source_location loc = std::source_location::current();
Expand Down Expand Up @@ -105,19 +111,27 @@ TEST(std_test, optional) {
TEST(std_test, expected) {
#ifdef __cpp_lib_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<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')");
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_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));
#endif
}
Expand Down
50 changes: 7 additions & 43 deletions test/xchar-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -562,49 +562,6 @@ TEST(locale_test, int_formatter) {
EXPECT_EQ(fmt::to_string(buf), "12,345");
}

FMT_BEGIN_NAMESPACE
template <class charT> struct formatter<std::complex<double>, charT> {
private:
detail::dynamic_format_specs<char> specs_;

public:
FMT_CONSTEXPR typename basic_format_parse_context<charT>::iterator parse(
basic_format_parse_context<charT>& ctx) {
auto end = parse_format_specs(ctx.begin(), ctx.end(), specs_, ctx,
detail::type::float_type);
detail::parse_float_type_spec(specs_);
return end;
}

template <class FormatContext>
typename FormatContext::iterator format(const std::complex<double>& c,
FormatContext& ctx) const {
auto specs = specs_;
detail::handle_dynamic_spec<detail::precision_checker>(
specs.precision, specs.precision_ref, ctx);
auto fspecs = std::string();
if (specs.precision > 0) fspecs = fmt::format(".{}", specs.precision);
if (specs.type == presentation_type::fixed) fspecs += 'f';
auto real = fmt::format(ctx.locale().template get<std::locale>(),
fmt::runtime("{:" + fspecs + "}"), c.real());
auto imag = fmt::format(ctx.locale().template get<std::locale>(),
fmt::runtime("{:" + fspecs + "}"), c.imag());
auto fill_align_width = std::string();
if (specs.width > 0) fill_align_width = fmt::format(">{}", specs.width);
return fmt::format_to(ctx.out(), runtime("{:" + fill_align_width + "}"),
c.real() != 0 ? fmt::format("({}+{}i)", real, imag)
: fmt::format("{}i", imag));
}
};
FMT_END_NAMESPACE

TEST(locale_test, complex) {
std::string s = fmt::format("{}", std::complex<double>(1, 2));
EXPECT_EQ(s, "(1+2i)");
EXPECT_EQ(fmt::format("{:.2f}", std::complex<double>(1, 2)), "(1.00+2.00i)");
EXPECT_EQ(fmt::format("{:8}", std::complex<double>(1, 2)), " (1+2i)");
}

TEST(locale_test, chrono_weekday) {
auto loc = get_locale("es_ES.UTF-8", "Spanish_Spain.1252");
auto loc_old = std::locale::global(loc);
Expand All @@ -625,6 +582,13 @@ TEST(locale_test, sign) {
EXPECT_EQ(fmt::format(std::locale(), L"{:L}", -50), L"-50");
}

TEST(std_test_xchar, complex) {
auto s = fmt::format(L"{}", std::complex<double>(1, 2));
EXPECT_EQ(s, L"(1,2)");
EXPECT_EQ(fmt::format(L"{:.2f}", std::complex<double>(1, 2)), L"(1.00,2.00)");
EXPECT_EQ(fmt::format(L"{:8}", std::complex<double>(1, 2)), L"(1,2) ");
}

TEST(std_test_xchar, optional) {
# ifdef __cpp_lib_optional
EXPECT_EQ(fmt::format(L"{}", std::optional{L'C'}), L"optional(\'C\')");
Expand Down
Loading