diff --git a/doc/snippets/Utility.cpp b/doc/snippets/Utility.cpp index 89e0ec015..5d98a1f38 100644 --- a/doc/snippets/Utility.cpp +++ b/doc/snippets/Utility.cpp @@ -319,6 +319,21 @@ else /* [Debug-usage] */ } +{ +auto sendMessage = [](const std::string &) {}; +/* [Debug-ostream-delegation] */ +using Utility::OstreamDebug::operator<<; +/// unfinished +Containers::Array array{Containers::InPlaceInit, { 0.1f, 22.22f, 3.14f }}; +std::cout << "array = " << array << std::endl; + +std::ostringstream o; +o << array << std::endl; +sendMessage(o.str()); +/// unfinished +/* [Debug-ostream-delegation] */ +} + { /* [Debug-scoped-output] */ std::ostringstream debugOut, errorOut; diff --git a/src/Corrade/Utility/Debug.h b/src/Corrade/Utility/Debug.h index 7c6a3f5ae..e3df3c6fd 100644 --- a/src/Corrade/Utility/Debug.h +++ b/src/Corrade/Utility/Debug.h @@ -82,6 +82,17 @@ type using @ref Debug. Note that printing @ref std::vector or @ref std::map containers is already possible with the generic iterable container support in @ref Corrade/Utility/Debug.h. +@subsection Utility-Debug-stl-ostream-delegation Ostream Delegation + +@ref Corrade/Utility/DebugStl.h also provides an @ref std::ostream @cpp +operator<<() @ce for printing builtin types: + +@snippet Utility.cpp Debug-ostream-delegation + +It must be brought into the current namespace by a using declaration: + +@cpp using Corrade::Utility::OstreamDebug::operator<<; @ce + @section Utility-Debug-scoped-output Scoped output redirection Output specified in class constructor is used for all instances created during diff --git a/src/Corrade/Utility/DebugStl.h b/src/Corrade/Utility/DebugStl.h index 823bf5747..9aca008a6 100644 --- a/src/Corrade/Utility/DebugStl.h +++ b/src/Corrade/Utility/DebugStl.h @@ -103,9 +103,46 @@ template Debug& operator<<(Debug& debug, const std::tuple << std::declval()) and + decltype(DeclareLvalueReference << std::declval()) + + would yield "yes" in both cases, not telling us which one is better. Instead + we supply an object that's implicitly convertible to both as the first + argument, giving the overload resolution magic a chance to pick the better + fitting one. */ +struct OstreamOrDebug { + /*implicit*/ operator std::ostream&(); + /*implicit*/ operator Debug&(); +}; + +CORRADE_HAS_TYPE( + HasBestFittingOstreamOperator, + typename std::enable_if() << std::declval()), + std::add_lvalue_reference::type + >::value>::type +); + +CORRADE_HAS_TYPE( + HasBestFittingDebugOperator, + typename std::enable_if() << std::declval()), + std::add_lvalue_reference::type + >::value>::type +); + /* Used by Debug::operator<<(Implementation::DebugOstreamFallback&&) */ struct DebugOstreamFallback { - template /*implicit*/ DebugOstreamFallback(const T& t): applier(&DebugOstreamFallback::applyImpl), value(&t) {} + template< + class T, + typename = typename std::enable_if::value>::type + > /*implicit*/ DebugOstreamFallback(const T& t): applier(&DebugOstreamFallback::applyImpl), value(&t) {} void apply(std::ostream& s) const { (this->*applier)(s); @@ -128,6 +165,26 @@ struct DebugOstreamFallback { CORRADE_UTILITY_EXPORT Debug& operator<<(Debug& debug, Implementation::DebugOstreamFallback&& value); #endif +namespace OstreamDebug { + +/** +@brief Print a builtin type to an ostream + +Allows calls like @cpp std::cout << Magnum::Vector2{0.2, 3.14}; @ce to be +delegated to a @ref Debug object. Creates a @ref Debug object on every call. +*/ +template +typename std::enable_if< + Implementation::HasBestFittingDebugOperator::value, + std::ostream +>::type &operator<<(std::ostream &os, const T &val) { + Corrade::Utility::Debug debug{&os, Corrade::Utility::Debug::Flag::NoNewlineAtTheEnd}; + debug << val; + return os; +} + +} + }} #endif diff --git a/src/Corrade/Utility/Test/DebugTest.cpp b/src/Corrade/Utility/Test/DebugTest.cpp index 06589cf3a..a28967e98 100644 --- a/src/Corrade/Utility/Test/DebugTest.cpp +++ b/src/Corrade/Utility/Test/DebugTest.cpp @@ -31,7 +31,7 @@ #include #include "Corrade/TestSuite/Tester.h" -#include "Corrade/Utility/Debug.h" +#include "Corrade/Containers/Array.h" #include "Corrade/Utility/DebugStl.h" #ifndef CORRADE_TARGET_EMSCRIPTEN @@ -82,6 +82,12 @@ struct DebugTest: TestSuite::Tester { void ostreamFallback(); void ostreamFallbackPriority(); + void ostreamDelegationInternalUsing(); + void ostreamDelegationExternalUsing(); + void ostreamDelegationCyclicDependency(); + void ostreamDelegationPriority(); + void ostreamDelegationPriorityImplicitConversion(); + void scopedOutput(); void debugColor(); @@ -151,6 +157,12 @@ DebugTest::DebugTest() { &DebugTest::ostreamFallback, &DebugTest::ostreamFallbackPriority, + &DebugTest::ostreamDelegationInternalUsing, + &DebugTest::ostreamDelegationExternalUsing, + &DebugTest::ostreamDelegationCyclicDependency, + &DebugTest::ostreamDelegationPriority, + &DebugTest::ostreamDelegationPriorityImplicitConversion, + &DebugTest::scopedOutput, &DebugTest::debugColor, @@ -819,6 +831,62 @@ void DebugTest::ostreamFallbackPriority() { CORRADE_COMPARE(out.str(), "baz from Debug\n"); } +namespace OstreamDelegationNamespace0 { + struct Corge { + int i; + }; + + inline Debug& operator<<(Debug& debug, const Corge& val) { + return debug << val.i << "corge from Debug"; + } +} + +void DebugTest::ostreamDelegationInternalUsing() { + using OstreamDebug::operator<<; + std::ostringstream out; + out << OstreamDelegationNamespace0::Corge{42}; + CORRADE_COMPARE(out.str(), "42 corge from Debug"); +} + +namespace OstreamDelegationNamespace1 { + struct Grault { + int i; + }; + + inline Debug& operator<<(Debug& debug, const Grault& val) { + return debug << val.i << "grault from Debug"; + } + + using OstreamDebug::operator<<; +} + +void DebugTest::ostreamDelegationExternalUsing() { + std::ostringstream out; + out << OstreamDelegationNamespace1::Grault{36}; + CORRADE_COMPARE(out.str(), "36 grault from Debug"); +} + +struct ClassWithoutStreamOperator {}; + +void DebugTest::ostreamDelegationCyclicDependency() { + CORRADE_VERIFY(!Implementation::HasBestFittingOstreamOperator::value); + CORRADE_VERIFY(!Implementation::HasBestFittingDebugOperator::value); +} + +void DebugTest::ostreamDelegationPriority() { + std::ostringstream out; + out << Baz{}; + CORRADE_COMPARE(out.str(), "baz from ostream"); +} + +void DebugTest::ostreamDelegationPriorityImplicitConversion() { + using OstreamDebug::operator<<; + Containers::Array array{Containers::InPlaceInit, { 1, 2, 3 }}; + std::ostringstream out; + out << array; + CORRADE_COMPARE(out.str(), "{1, 2, 3}"); +} + void DebugTest::scopedOutput() { std::ostringstream debug1, debug2, warning1, warning2, error1, error2; @@ -955,9 +1023,9 @@ void DebugTest::sourceLocation() { #ifdef CORRADE_UTILITY_DEBUG_HAS_SOURCE_LOCATION CORRADE_COMPARE(out.str(), - __FILE__ ":947: hello\n" - __FILE__ ":949: and this is from another line\n" - __FILE__ ":951\n" + __FILE__ ":1015: hello\n" + __FILE__ ":1017: and this is from another line\n" + __FILE__ ":1019\n" "this no longer\n"); #else CORRADE_COMPARE(out.str(),