diff --git a/include/magic_enum/magic_enum_iostream.hpp b/include/magic_enum/magic_enum_iostream.hpp index 894d843aa..b536fcda7 100644 --- a/include/magic_enum/magic_enum_iostream.hpp +++ b/include/magic_enum/magic_enum_iostream.hpp @@ -37,10 +37,31 @@ #ifndef MAGIC_ENUM_USE_STD_MODULE # include +# include +# include #endif namespace magic_enum { +namespace detail { + +// Formatted insertion, so width, fill and adjustfield apply to the name as a whole. +template +std::basic_ostream& write_name(std::basic_ostream& os, string_view name) { + if constexpr (std::is_same_v) { + return os << std::basic_string_view{name.data(), name.size()}; + } else { + std::basic_string s; + s.reserve(name.size()); + for (const auto c : name) { + s.push_back(static_cast(c)); + } + return os << s; + } +} + +} // namespace magic_enum::detail + namespace ostream_operators { template = 0> @@ -51,17 +72,11 @@ std::basic_ostream& operator<<(std::basic_ostream& o if constexpr (detail::supported::value) { if constexpr (detail::subtype_v == detail::enum_subtype::flags) { if (const auto name = enum_flags_name(value); !name.empty()) { - for (std::size_t i = 0; i < name.size(); ++i) { - os.put(name.data()[i]); - } - return os; + return detail::write_name(os, string_view{name.data(), name.size()}); } } else { if (const auto name = enum_name(value); !name.empty()) { - for (std::size_t i = 0; i < name.size(); ++i) { - os.put(name.data()[i]); - } - return os; + return detail::write_name(os, name); } } } diff --git a/test/test.cpp b/test/test.cpp index 2620bc220..128b60efa 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include #include @@ -1250,6 +1251,27 @@ TEST_CASE("ostream_operators") { #endif } + SUBCASE("field width applies to the name") { + using namespace magic_enum::ostream_operators; + + std::ostringstream named; + named << std::setfill('_') << std::setw(6) << Color::GREEN << '|' << std::left << std::setw(6) << Color::BLUE << '|' << Color::BLUE << '|'; + REQUIRE(named.str() == "_GREEN|BLUE__|BLUE|"); + + std::ostringstream fallback; + fallback << std::setfill('_') << std::setw(6) << static_cast(0) << '|' << std::setw(6) << std::make_optional(Color::BLUE) << '|'; + REQUIRE(fallback.str() == "_____0|__BLUE|"); + + std::basic_ostringstream> custom; + custom << std::setfill('_') << std::setw(6) << Color::BLUE << '|'; + const auto result = custom.str(); + REQUIRE(std::string(result.data(), result.size()) == "__BLUE|"); + + std::wostringstream wide; + wide << std::setfill(L'_') << std::setw(6) << Color::BLUE << L'|'; + REQUIRE(wide.str() == L"__BLUE|"); + } + SUBCASE("short underlying type formatting") { using namespace magic_enum::ostream_operators; enum class Small : short { Named = 1 }; diff --git a/test/test_aliases.cpp b/test/test_aliases.cpp index 012a0344e..168fe06d0 100644 --- a/test/test_aliases.cpp +++ b/test/test_aliases.cpp @@ -10,6 +10,7 @@ #include "test_helpers.hpp" +#include #include using namespace magic_enum; @@ -145,3 +146,15 @@ TEST_CASE("string containers") { stream << colors; REQUIRE(stream.str() == "RED|BLUE"); } + +TEST_CASE("ostream_operators") { + using namespace magic_enum::ostream_operators; + + std::ostringstream narrow; + narrow << std::setfill('_') << std::setw(10) << (Color::RED | Color::BLUE) << '|'; + REQUIRE(narrow.str() == "__RED|BLUE|"); + + std::wostringstream wide; + wide << std::setfill(L'_') << std::setw(10) << (Color::RED | Color::BLUE) << L'|'; + REQUIRE(wide.str() == L"__RED|BLUE|"); +} diff --git a/test/test_flags.cpp b/test/test_flags.cpp index 802ca2b03..3b8bf2161 100644 --- a/test/test_flags.cpp +++ b/test/test_flags.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include @@ -780,6 +781,13 @@ TEST_CASE("ostream_operators") { REQUIRE(stream.str() == "false"); } + SUBCASE("field width applies to the whole name") { + using namespace magic_enum::ostream_operators; + std::ostringstream stream; + stream << std::setfill('_') << std::setw(10) << (Color::BLUE | Color::RED) << '|' << std::left << std::setw(10) << (Color::BLUE | Color::RED) << '|'; + REQUIRE(stream.str() == "__RED|BLUE|RED|BLUE__|"); + } + require_ostream(std::make_optional(Color::RED), "RED"); require_ostream(Color::GREEN, "GREEN"); require_ostream(Color::BLUE, "BLUE"); diff --git a/test/test_wchar_t.cpp b/test/test_wchar_t.cpp index 5d180aa1a..2248e846b 100644 --- a/test/test_wchar_t.cpp +++ b/test/test_wchar_t.cpp @@ -17,6 +17,7 @@ #include #include +#include #include enum class Color { RED = -12, GREEN = 7, BLUE = 15 }; @@ -150,6 +151,18 @@ TEST_CASE("wstring_view lifetime and null termination") { } TEST_CASE("ostream_operators") { + SUBCASE("field width applies to the name") { + using namespace magic_enum::ostream_operators; + + std::wostringstream wide; + wide << std::setfill(L'_') << std::setw(6) << Color::RED << L'|'; + REQUIRE(wide.str() == L"___red|"); + + std::ostringstream narrow; + narrow << std::setfill('_') << std::setw(6) << Color::RED << '|'; + REQUIRE(narrow.str() == "___red|"); + } + require_ostream(std::make_optional(Color::RED), L"red"); require_ostream(Color::GREEN, L"GREEN"); require_ostream(Color::BLUE, L"BLUE");