-
Notifications
You must be signed in to change notification settings - Fork 848
Comparable: Enable comparison operators based on a ternary comparison #4391
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
33f6595 to
9398537
Compare
|
There is a cheesy tick you have to use if both a base class and a class derived from it both are comparable, illustrated in this code; If Y inherits directly from ts::comparable, you get this warning from gcc (that we treat as an error): unit_tests/test_ts_meta.cc:260:7: error: direct base 'ts::Comparable' inaccessible in 'Y' due to ambiguity [-Werror] |
|
I'll take a look at that. |
9398537 to
bc7575f
Compare
|
Two things - For This can also be fixed by using Because of these two (IMHO easy to implement) avoidance strategies, I dont' think this rates as a real problem. P.S. I added some test cases to demonstrate these points. I verified that if the |
|
A virtual base class adds a hidden v pointer to every class instance if it's not already present. |
| template <typename T, typename U> | ||
| auto | ||
| operator==(T const &lhs, U const &rhs) -> | ||
| typename std::enable_if<std::is_base_of<Comparable, T>::value || std::is_base_of<Comparable, U>::value, bool>::type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider using std::is_convertable in place of std::is_base_of . This allows the alternative of tagging a class for comparison using a dummy conversion operator:
operator Comparable ();
This avoids the ambiguous base class error if both a base class and a class derived from it need to be tagged as comparable. It also allows a derived class to suppress inheritance of the comparison operators of a base class with:
operator Comparable () = delete;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I considered that but think it's a bad idea because it could easily cause the Comparable machinery to fire up in places it shouldn't. "if both a base class and a class derived from it need to be tagged as comparable" - IMHO that is never the case. If the base class is tagged as comparable, there is never a need to tag a class derived from it. Just don't do it. That seems a simpler solution.
|
This is kinda crazy, I really don't think we should go into this land of obscurity. |
|
I think it seems quite non-obscure and useful. Why would you favor having to write out the standard comparison operators by hand, when they're all completely boiler plate? It's an easy place to make mistakes and we have in fact had bugs for that reason. This is exactly the kind of mindless programming that should be automated. |
|
Why aren't the unit tests for this in src/tscpp/util/unit_tests? |
|
Obscurity #1: The test_ts_meta.cc still compiles if I make this change to it: |
|
Obscurity #2: If I change test_ts_meta.cc like this: (An error that one could easily make in a moment of absent-mindedness) the resulting compiler error messages are: (Additional 420 lines of diagnostic output deleted for brevity.) |
"If you see this" "then you have not provided the correct ternary compare operator." |
|
I had a chance to experiment on my theory about why the change still compiles. I was half right. It continues to work for 2 different reasons.
Changing both functions and adding |
bc7575f to
4f34b7c
Compare
|
I've redone the PR a bit.
|
4f34b7c to
038668c
Compare
|
Good changes, but this does support @zwoop 's point about obscurity. What do you see as the worse problems with the approach using macros, that outweigh these problems? |
|
I don't see any of this as a big problem. I think the excessive error messages are better than obscure and hard to detect failures, especially with a bit of documentation. Note such errors are basically one offs - they would happen very early in the use cycle and once fixed don't recur. The other changes are basically just testing, not actual interface changes. Other advantages over macros are
|
|
I don't see the point about the argument conversions. The ease of use comes at a cost. If you make types A and B comparable, and type C comparable with itself, you get the scary error spew if you compare A with C, B with C, A with itself. It only takes one line of code to make a free-standing cmp() that calls a member cmp(). I think the scenarios where lack of macro scoping could cause run-time errors are rare. I think they'd mostly result from defining an parameterless macro to translate into a single identifier. The compile-time errors I doubt would be harder to deal with than what's above. I think C++ is unavoidably a hodgepodge. Templates should be preferred over macros, but not fetishized. There are reasons why the preprocessor has not been fully deprecated. |
|
Lack of macro scoping and runtime errors introduced by bad use of macros are independent issues, one is not cause by the other. In your own example, the key point is only 3 lines in to the error list - "unit_tests/test_ts_meta.cc:241:3: required from here". I would also note that ascribing opinions that differ from yours to psychological problems like "fetishized" is counter productive, because that is the refugee of people who don't have substantive arguments. |
038668c to
bc3bc71
Compare
|
Sorry I didn't mean it literally. |
|
I find macros very problematic and (for me at least) noticeable worse than all but the most complex template implementation. Generally the template issue is quite early in the error text and so most of it can be ignored. I updated the comments to make the error output a bit clearer. I also find template work to be more flexible and adaptable and I place a high value on those attributes. |
|
Something else that gives me heartburn is trying to understand the C++ rules for using names that come from the surrounding context when using templates (like "cmp" in this PR). Here is an example: |
… function or method.
bc3bc71 to
cf9fe4c
Compare
|
Does this mean you're bending the knee to #4262 ? |
Given a ternary comparison (e.g., -1, 0, 1 result), this creates the 6 standard binary comparison operators. This works for same type and cross type comparisons.
Depends on #4140. An alternative approach for #4262.