Skip to content

Conversation

rune-scape
Copy link

Compile errors are much clearer for types without a stream operator, and avoids being an actual catch-all.

This is a bit of a selfish pr, as my code to translate Debug operator<<s to std::ostream operator<<s is bugged when there is a catchall for Debug stream operator<<s.
Both stream fallbacks use each other. This results in being able to compile cout << UnprintableClass{}.
At runtime it gets stuck in recursion, flipflopping between both operator<<s. I thought that was pretty interesting.

Please tell me if I made any formatting mistakes.

@codecov-io
Copy link

codecov-io commented Jan 25, 2020

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 97.53%. Comparing base (48091a1) to head (ccb7bcd).
Report is 2120 commits behind head on master.

Additional details and impacted files
@@            Coverage Diff             @@
##           master      #88      +/-   ##
==========================================
+ Coverage   97.42%   97.53%   +0.10%     
==========================================
  Files          92       92              
  Lines        6412     6285     -127     
==========================================
- Hits         6247     6130     -117     
+ Misses        165      155      -10     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@mosra mosra added this to the 2020.0a milestone Jan 26, 2020
Copy link
Owner

@mosra mosra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The extremely huge compiler errors on a mismatched operator<< were a pain, so thank you for making everyone's life better :)

This is a bit of a selfish pr

Why? :) When I saw the notification, I actually thought you'd be adding the inverse operator here -- making Debug-printable types work with std::ostream. I'm happy to accept that, you'd be definitely not the only one using it.


public:
static constexpr bool value = decltype(check<T>(nullptr))::value;
};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's a (admittedly weirdly named) CORRADE_HAS_TYPE(className, typeExpression) macro that already contains the above functionality, but your variant seems to be a lot simpler than what I stole from somewhere back in the day :) If I fix the macro to have the second parameter variadic (so the comma doesn't break it), I think we could then reduce the above to be just this (containing the potentially hard-to-understand template magic in a single place):

CORRADE_HAS_TYPE(HasOstreamOperator, typename std::is_same<
    decltype(declareLvalueReference<std::ostream>() << std::declval<C>()),
    std::add_lvalue_reference<std::ostream>::type
>::type)

(Also adapting the naming to what Corrade uses for type traits.) I hope I didn't overlook something that would make this impossible tho.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CORRADE_HAS_TYPE() is fixed to allow this in 8d4eb5b, and I think it would work with a minor change:

CORRADE_HAS_TYPE(HasOstreamOperator, typename std::enable_if<std::is_same<
    decltype(declareLvalueReference<std::ostream>() << std::declval<C>()),
    std::add_lvalue_reference<std::ostream>::type
>::value>::type)

I assume you have some way to test the infinite recursion already -- can you confirm? :)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! works perfectly. A very useful macro too

simplified HasOstreamOperator by using CORRADE_HAS_TYPE macro
renamed both to magnum schemes
@rune-scape
Copy link
Author

I figured I should separate this from the feature adding one, but if you want I can add it in.

@mosra
Copy link
Owner

mosra commented Jan 28, 2020

Adding the inverse operator would have a good side effect of testing that the above SFINAE restriction actually works and there's no infinite recursion anymore (while this alone doesn't check for that, and so I'm afraid of breaking it by accident).

So, by all means, add it in :)

@rune-scape rune-scape changed the title Utility::Debug: DebugOstreamFallback enable if has ostream operator Utility::Debug: add std::ostream output operator call converter Jan 29, 2020
@rune-scape
Copy link
Author

The extra checks are to avoid ambiguous calls

Copy link
Owner

@mosra mosra left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, the diff is so nicely small and tidy that at first I thought I was looking at the previous state with the operator<< not added yet :)

@brief Like @cpp std::declval @ce, but declares an lvalue reference
*/
template<typename T>
typename std::add_lvalue_reference<T>::type DeclareLvalueReference() noexcept;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: I'm usually not putting a newline after template<..>, keeping the whole signature a single line. (Or generally not adding a newline unless really needed to keep the code vertically terse.)


}}

template<typename T>
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One last thing: can you

  • add a short documentation block for this new function,
  • mention this in the class documentation so people get to know it exists -- there's a "Printing STL types" section, so maybe extending that a bit ("Printing STL types, interaction with iostreams"?)
  • and add a test in Test/DebugTest.cpp? There's ostreamFallback and ostreamFallbackPriority, so something similar (ostreamDelegate?) and also similarly checking the priority works for the inverse case, picking the ostream operator over the Debug one -- btw., don't forget to list the two new methods in the addTests() so these get properly called

Thank you! 👍

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes! wow ostreamDelegate is way better than any name I considered

@rune-scape
Copy link
Author

a problem:
implicit conversions take priority over the operator<<(only because HasOstreamOperator only checks for any possible ostream print operator), meaning Container::Array get printed as a void *

a fix is making HasOstreamOperatorExact and HasOstreamMemberOperatorExact, and checking like 4 versions of each... but like.. no

@mosra
Copy link
Owner

mosra commented Feb 5, 2020

@bowling-allie finally today I got a totally random idea and it seems it works -- or at least made the test pass. Full details in the 3 new commits I just pushed to your branch (I hope that's okay, hehe).

Can you check with your own code if everything is still behaving as expected and no circular recursion or anything happens again?

EDIT: argh, Clang and MSVC doesn't like this ... ambiguous overload :/

@mosra mosra modified the milestones: 2020.0a, 2020.0b Jun 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: TODO
Development

Successfully merging this pull request may close these issues.

3 participants