Skip to content

Commit

Permalink
Make ostream operators opt in to reduce the risk of ODR violations
Browse files Browse the repository at this point in the history
  • Loading branch information
vitaut committed Feb 4, 2022
1 parent 8a21e32 commit f055ebb
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 43 deletions.
4 changes: 4 additions & 0 deletions include/fmt/core.h
Original file line number Diff line number Diff line change
Expand Up @@ -1016,7 +1016,11 @@ struct fallback_formatter {
// Specifies if T has an enabled fallback_formatter specialization.
template <typename T, typename Char>
using has_fallback_formatter =
#ifdef FMT_DEPRECATED_OSTREAM
std::is_constructible<fallback_formatter<T, Char>>;
#else
std::false_type;
#endif

struct view {};

Expand Down
3 changes: 3 additions & 0 deletions include/fmt/ostream.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
};
} // namespace detail

template <typename T, typename Char = char>
using ostream_formatter = detail::fallback_formatter<T, Char>;

FMT_MODULE_EXPORT
template <typename Char>
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
Expand Down
60 changes: 22 additions & 38 deletions test/ostream-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,17 @@ std::ostream& operator<<(std::ostream& os, streamable_enum) {

enum unstreamable_enum {};

struct empty_test {};
std::ostream& operator<<(std::ostream& os, empty_test) { return os << ""; }

namespace fmt {
template <> struct formatter<test_string> : ostream_formatter<test_string> {};
template <> struct formatter<date> : ostream_formatter<date> {};
template <>
struct formatter<streamable_enum> : ostream_formatter<streamable_enum> {};
template <> struct formatter<empty_test> : ostream_formatter<empty_test> {};
} // namespace fmt

TEST(ostream_test, enum) {
EXPECT_EQ("streamable_enum", fmt::format("{}", streamable_enum()));
EXPECT_EQ("0", fmt::format("{}", unstreamable_enum()));
Expand Down Expand Up @@ -86,9 +97,6 @@ TEST(ostream_test, format_specs) {
EXPECT_EQ("te", fmt::format("{0:.{1}}", test_string("test"), 2));
}

struct empty_test {};
std::ostream& operator<<(std::ostream& os, empty_test) { return os << ""; }

TEST(ostream_test, empty_custom_output) {
EXPECT_EQ("", fmt::format("{}", empty_test()));
}
Expand Down Expand Up @@ -184,6 +192,9 @@ template <typename T> struct formatter<test_template<T>> : formatter<int> {
return formatter<int>::format(2, ctx);
}
};

template <>
struct formatter<fmt_test::abc> : ostream_formatter<fmt_test::abc> {};
} // namespace fmt

TEST(ostream_test, template) {
Expand Down Expand Up @@ -214,41 +225,6 @@ TEST(ostream_test, disable_builtin_ostream_operators) {
EXPECT_EQ("foo", fmt::format("{}", convertible<const char*>("foo")));
}

struct explicitly_convertible_to_string_like {
template <typename String,
typename = typename std::enable_if<std::is_constructible<
String, const char*, size_t>::value>::type>
explicit operator String() const {
return String("foo", 3u);
}
};

std::ostream& operator<<(std::ostream& os,
explicitly_convertible_to_string_like) {
return os << "bar";
}

TEST(ostream_test, format_explicitly_convertible_to_string_like) {
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
}

#ifdef FMT_USE_STRING_VIEW
struct explicitly_convertible_to_std_string_view {
explicit operator fmt::detail::std_string_view<char>() const {
return {"foo", 3u};
}
};

std::ostream& operator<<(std::ostream& os,
explicitly_convertible_to_std_string_view) {
return os << "bar";
}

TEST(ostream_test, format_explicitly_convertible_to_std_string_view) {
EXPECT_EQ("bar", fmt::format("{}", explicitly_convertible_to_string_like()));
}
#endif // FMT_USE_STRING_VIEW

struct streamable_and_convertible_to_bool {
operator bool() const { return true; }
};
Expand Down Expand Up @@ -285,6 +261,10 @@ std::ostream& operator<<(std::ostream& os, copyfmt_test) {
return os << "foo";
}

namespace fmt {
template <> struct formatter<copyfmt_test> : ostream_formatter<copyfmt_test> {};
} // namespace fmt

TEST(ostream_test, copyfmt) {
EXPECT_EQ("foo", fmt::format("{}", copyfmt_test()));
}
Expand All @@ -306,6 +286,10 @@ struct abstract {
}
};

namespace fmt {
template <> struct formatter<abstract> : ostream_formatter<abstract> {};
} // namespace fmt

void format_abstract_compiles(const abstract& a) {
fmt::format(FMT_COMPILE("{}"), a);
}
Expand Down
5 changes: 0 additions & 5 deletions test/printf-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
#include <climits>
#include <cstring>

#include "fmt/ostream.h"
#include "fmt/xchar.h"
#include "gtest-extra.h"
#include "util.h"
Expand Down Expand Up @@ -533,10 +532,6 @@ TEST(printf_test, wide_string) {
EXPECT_EQ(L"abc", fmt::sprintf(L"%s", L"abc"));
}

TEST(printf_test, printf_custom) {
EXPECT_EQ("abc", test_sprintf("%s", test_string("abc")));
}

TEST(printf_test, vprintf) {
fmt::format_arg_store<fmt::printf_context, int> as{42};
fmt::basic_format_args<fmt::printf_context> args(as);
Expand Down
6 changes: 6 additions & 0 deletions test/xchar-test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ std::wostream& operator<<(std::wostream& os, streamable_enum) {
return os << L"streamable_enum";
}

namespace fmt {
template <>
struct formatter<streamable_enum, wchar_t>
: ostream_formatter<streamable_enum, wchar_t> {};
} // namespace fmt

enum unstreamable_enum {};

TEST(xchar_test, enum) {
Expand Down

0 comments on commit f055ebb

Please sign in to comment.