Skip to content
31 changes: 23 additions & 8 deletions include/magic_enum/magic_enum_iostream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,31 @@

#ifndef MAGIC_ENUM_USE_STD_MODULE
# include <iosfwd>
# include <string>
# include <string_view>
#endif

namespace magic_enum {

namespace detail {

// Formatted insertion, so width, fill and adjustfield apply to the name as a whole.
template <typename Char, typename Traits>
std::basic_ostream<Char, Traits>& write_name(std::basic_ostream<Char, Traits>& os, string_view name) {
if constexpr (std::is_same_v<Char, char_type>) {
return os << std::basic_string_view<Char, Traits>{name.data(), name.size()};
} else {
std::basic_string<Char, Traits> s;
s.reserve(name.size());
for (const auto c : name) {
s.push_back(static_cast<Char>(c));
}
return os << s;
}
}

} // namespace magic_enum::detail

namespace ostream_operators {

template <typename Char, typename Traits, typename E, detail::enable_if_t<E, int> = 0>
Expand All @@ -51,17 +72,11 @@ std::basic_ostream<Char, Traits>& operator<<(std::basic_ostream<Char, Traits>& o
if constexpr (detail::supported<D>::value) {
if constexpr (detail::subtype_v<D> == detail::enum_subtype::flags) {
if (const auto name = enum_flags_name<D>(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<D>(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);
}
}
}
Expand Down
22 changes: 22 additions & 0 deletions test/test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include <array>
#include <cctype>
#include <iomanip>
#include <memory>
#include <string>
#include <string_view>
Expand Down Expand Up @@ -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<Color>(0) << '|' << std::setw(6) << std::make_optional(Color::BLUE) << '|';
REQUIRE(fallback.str() == "_____0|__BLUE|");

std::basic_ostringstream<char, CustomCharTraits<char>> 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 };
Expand Down
13 changes: 13 additions & 0 deletions test/test_aliases.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

#include "test_helpers.hpp"

#include <iomanip>
#include <sstream>

using namespace magic_enum;
Expand Down Expand Up @@ -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|");
}
8 changes: 8 additions & 0 deletions test/test_flags.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

#include <array>
#include <cctype>
#include <iomanip>
#include <string>
#include <string_view>

Expand Down Expand Up @@ -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");
Expand Down
13 changes: 13 additions & 0 deletions test/test_wchar_t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

#include <array>
#include <cctype>
#include <iomanip>
#include <sstream>

enum class Color { RED = -12, GREEN = 7, BLUE = 15 };
Expand Down Expand Up @@ -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");
Expand Down
Loading