From 0555b0eacbc56df1fd762c6aa87bb84be9e4ce7e Mon Sep 17 00:00:00 2001 From: Abseil Team Date: Fri, 9 Oct 2020 06:07:04 -0400 Subject: [PATCH] Googletest export Improve lookup of operator<< for user types Without this fix, trying to use this class with googletest struct Foo {}; template OutputStream& operator<<(OutputStream& os, const Foo&) { os << "TemplatedStreamableInFoo"; return os; } results in an ambiguity error between the class' operator<< and the operator<< in gtest-printers.h removed in this CL. This fix also enables implicit conversions to happen, so that e.g. we will find the base class operator<< if a subclass has no operator<< of its own. PiperOrigin-RevId: 336261221 --- googletest/include/gtest/gtest-printers.h | 52 +++++++++------------ googletest/test/googletest-printers-test.cc | 37 +++++++++++++++ 2 files changed, 59 insertions(+), 30 deletions(-) diff --git a/googletest/include/gtest/gtest-printers.h b/googletest/include/gtest/gtest-printers.h index 99129a53..f697e2f0 100644 --- a/googletest/include/gtest/gtest-printers.h +++ b/googletest/include/gtest/gtest-printers.h @@ -192,43 +192,34 @@ struct PointerPrinter { } }; -namespace internal_stream { +namespace internal_stream_operator_without_lexical_name_lookup { -struct Sentinel; -template -Sentinel* operator<<(::std::basic_ostream& os, const T& x); - -// Check if the user has a user-defined operator<< for their type. -// -// We put this in its own namespace to inject a custom operator<< that allows us -// to probe the type's operator. -// -// Note that this operator<< takes a generic std::basic_ostream type instead of the more restricted std::ostream. If -// we define it to take an std::ostream instead, we'll get an -// "ambiguous overloads" compiler error when trying to print a type -// Foo that supports streaming to std::basic_ostream, as the compiler cannot tell whether -// operator<<(std::ostream&, const T&) or -// operator<<(std::basic_stream, const Foo&) is more -// specific. -template -constexpr bool UseStreamOperator() { - return !std::is_same() - << std::declval()), - Sentinel*>::value; -} - -} // namespace internal_stream +// The presence of an operator<< here will terminate lexical scope lookup +// straight away (even though it cannot be a match because of its argument +// types). Thus, the two operator<< calls in StreamPrinter will find only ADL +// candidates. +struct LookupBlocker {}; +void operator<<(LookupBlocker, LookupBlocker); struct StreamPrinter { - template ()>::type> + template ::value>::type, + // Only accept types for which we can find a streaming operator via + // ADL (possibly involving implicit conversions). + typename = decltype(std::declval() + << std::declval())> static void PrintValue(const T& value, ::std::ostream* os) { + // Call streaming operator found by ADL, possibly with implicit conversions + // of the arguments. *os << value; } }; +} // namespace internal_stream_operator_without_lexical_name_lookup + struct ProtobufPrinter { // We print a protobuf using its ShortDebugString() when the string // doesn't exceed this many characters; otherwise we print it using @@ -308,7 +299,8 @@ template void PrintWithFallback(const T& value, ::std::ostream* os) { using Printer = typename FindFirstPrinter< T, void, ContainerPrinter, FunctionPointerPrinter, PointerPrinter, - StreamPrinter, ProtobufPrinter, ConvertibleToIntegerPrinter, + internal_stream_operator_without_lexical_name_lookup::StreamPrinter, + ProtobufPrinter, ConvertibleToIntegerPrinter, ConvertibleToStringViewPrinter, FallbackPrinter>::type; Printer::PrintValue(value, os); } diff --git a/googletest/test/googletest-printers-test.cc b/googletest/test/googletest-printers-test.cc index ba2befb8..c81af371 100644 --- a/googletest/test/googletest-printers-test.cc +++ b/googletest/test/googletest-printers-test.cc @@ -90,6 +90,18 @@ class BiggestIntConvertible { operator ::testing::internal::BiggestInt() const { return 42; } }; +// A parent class with two child classes. The parent and one of the kids have +// stream operators. +class ParentClass {}; +class ChildClassWithStreamOperator : public ParentClass {}; +class ChildClassWithoutStreamOperator : public ParentClass {}; +static void operator<<(std::ostream& os, const ParentClass&) { + os << "ParentClass"; +} +static void operator<<(std::ostream& os, const ChildClassWithStreamOperator&) { + os << "ChildClassWithStreamOperator"; +} + // A user-defined unprintable class template in the global namespace. template class UnprintableTemplateInGlobal { @@ -177,6 +189,17 @@ inline ::std::ostream& operator<<(::std::ostream& os, return os << "StreamableTemplateInFoo: " << x.value(); } +// A user-defined streamable type in a user namespace whose operator<< is +// templated on the type of the output stream. +struct TemplatedStreamableInFoo {}; + +template +OutputStream& operator<<(OutputStream& os, + const TemplatedStreamableInFoo& /*ts*/) { + os << "TemplatedStreamableInFoo"; + return os; +} + // A user-defined streamable but recursivly-defined container type in // a user namespace, it mimics therefore std::filesystem::path or // boost::filesystem::path. @@ -1201,6 +1224,20 @@ TEST(PrintStreamableTypeTest, TemplateTypeInUserNamespace) { Print(::foo::StreamableTemplateInFoo())); } +TEST(PrintStreamableTypeTest, TypeInUserNamespaceWithTemplatedStreamOperator) { + EXPECT_EQ("TemplatedStreamableInFoo", + Print(::foo::TemplatedStreamableInFoo())); +} + +TEST(PrintStreamableTypeTest, SubclassUsesSuperclassStreamOperator) { + ParentClass parent; + ChildClassWithStreamOperator child_stream; + ChildClassWithoutStreamOperator child_no_stream; + EXPECT_EQ("ParentClass", Print(parent)); + EXPECT_EQ("ChildClassWithStreamOperator", Print(child_stream)); + EXPECT_EQ("ParentClass", Print(child_no_stream)); +} + // Tests printing a user-defined recursive container type that has a << // operator. TEST(PrintStreamableTypeTest, PathLikeInUserNamespace) {