C++ Logo

std-proposals

Advanced search

Re: [std-proposals] Let spaceship return an int

From: Chris Gary <cgary512_at_[hidden]>
Date: Tue, 26 Sep 2023 23:04:46 -0600
It looks like Microsoft is already 66% of the way there...

With VS 17.7.4...

`
// #include <compare> <- absent. I also haven't installed the modules
library.
// compile with /std:c++latest

struct Test
{
  int operator <=>( const Test & )
  { return 0; }
};

int main( int argc, char **argv )
{
  Test a, b;

  // This compiles and results in a -1
  if( a <= b )
  {
    return -1;
  }

  // This compiles and skips the branch.
  if( a < b )
  {
    return -1;
  }

  // This does not, giving C2676.
  if( a != b || a == b )
  {
    return -2;
  }

  return 0;
}`

On Tue, Sep 26, 2023 at 9:34 PM Chris Gary <cgary512_at_[hidden]> wrote:

> This is still completely branch-free for NaN.
>
> From:
>
> `
> // user-defined, so it may or may not be horribly slow or return -50
> int compare( nan_able a, nan_able b )
> {
> if( isnan(a) || isnan(b) )
> { return -2; }
>
> // Otherwise equivalent to this:
> return sgn_as_int( a - b );
> }
>
> Operator equivalents:
>
> bool operator == ( nan_able a, nan_able b )
> {
> return compare( a, b ) == 0;
> }
> bool operator != ( nan_able a, nan_able b )
> {
> static const bool tbl_0[4]
> {
> false, // saturation of any other value to -2
> true, // less -> a != b
> false, // equal -> ! ( a != b )
> true // greater -> a != b
> };
> static const bool *tbl = &tbl_0[2];
> return tbl[ saturate_to_tbl( compare( a, b ) ) ];
> }
> bool operator < ( nan_able a, nan_able b )
> {
> return compare( a, b ) == -1;
> }
> bool operator <= ( nan_able a, nan_able b )
> {
> static const bool tbl_0[4]
> { false, true, true, false };
> static const bool *tbl = &tbl_0[2];
> return tbl[ saturate_to_tbl( compare( a, b ) ) ];
> }
> bool operator > ( nan_able a, nan_able b )
> {
> return compare( a, b ) > 0;
> }
> bool operator >= ( nan_able b, nan_able b )
> {
> static const bool tbl_0[4]
> { false, false, true, true };
> static const bool *tbl = &tbl_0[2];
> return tbl[ saturate_to_tbl( compare( a, b ) ) ];
> }`
>
> This handles the "NaN <op> NaN" == false convention with the worst case
> overhead of a branch-free saturation and table lookup if more than one
> possibility needs to be tested.
> I was hoping another compiler author might step in and offer something
> similar, then demonstrate why it is impossible to incorporate in some other
> way I did not consider.
>
> So, should it assume all value types are nan_able? Yes, since the proposed
> definition of the operator with the int return type is to check for
> containment in the set {-1,0,1}, which requires a saturate-then-lookup in
> this case to be robust. I can imagine there might be some challenges for an
> optimizer to overcome.
>
> Why not in the opening statement? I use isnan() and hardware exceptions
> (with hw floats) to catch the problems closer to where they happen. I
> thought this was advised as good practice.
> What happens when predicates start returning -2? I use something like an
> "is_unspec" test for relationships applied wherever a ternary is used in
> situations where that might be a problem.
>
> Consequently, I don't consider oddball comparison behavior an adequate
> error trapping mechanism. isnan is not defined as "!( a != a )", and I
> don't expect the same from a hw-based comparison.
>
> I fail to see why a strongly-typed ternary, mandatory for use with <=>,
> provides an adequate solution to this problem. Especially where the
> operator is user-defined.
> All it does is create what I consider to be a nuisance of a dependency.
>
> On Tue, Sep 26, 2023 at 7:40 PM Chris Gary <cgary512_at_[hidden]> wrote:
>
>> Its hard for me to say what inspired many of the responses I received,
>> aside from my clicking "reply to" and having a good bit of unproductive
>> discussion vanish into personal conversations with others. Yet, here, it is
>> not having problems putting your email on the CC list, and the message
>> board on the main...
>>
>> We have a set of 3 things in std::strong_ordering, and an implicit 4th
>> that is "not defined" or "unspecified". "Unspecified" is not an error, its
>> just UB, which we've all come to know and work with. Proving that an
>> arbitrary operator <=>, return type notwithstanding, will never result in
>> UB is ultimately undecidable (also obvious).
>>
>> Wherever a user-defined operator <=> suddenly produces an int, the
>> compiler can instead insert code to check against any of {-1,0,1} depending
>> on what was synthesized. No need for a header or privileged symbols in
>> namespace std. Again, I proposed this assuming a good deal of other things
>> were implied.
>>
>> The comment about "Hacking into Clang..." was meant for another thread,
>> the discussion in which seemed to creep into this one. At least a mention
>> of "polyfill" was made in one post I responded to, and that ended up here.
>>
>> This won't break or interact with modules as far as I can tell. In
>> general code, obviously something like decltype( a <=> b ), but then
>> wherever that is being done, it probably doesn't care about specific
>> names...
>>
>> To every complaint about "ABI breakage" etc... It won't break any
>> existing code to allow new code to define an int-valued <=>. Its just a
>> function by another name. The behavior of the compiler can be made
>> well-defined in either case.
>>
>> The comment in the opening about "all knowledge being integers" etc...
>> also obvious, was meant to set the tone to "this is always possible,
>> reasoning to the contrary must now confront an obvious truth." Why? I feel
>> the decision to impose semantics through a return type is bad design, and
>> thought the reasoning for its adoption was due to a narrow scope of
>> understanding. Started by yours truly, all bad ideas that just ended up
>> gaslighting many very negative responses, then more confused negativity
>> from myself, etc...
>>
>> I would need a specific example of where, if some new code were allowed
>> to define an int-valued operator <=>, this would make it impossible for it
>> to build against other code. I see this as analogous to
>> "std::strong_ordering compare( thing a, thing b )" being in the same
>> overload set as "int compare( different_thing a, different_thing b )". Type
>> conversion issues aside; two functions, with the same name, different
>> arguments and a different return type haven't been an issue before.
>>
>> There is too much to review from this mess right now. I felt an
>> explanation was warranted from me. If an example has already been posted,
>> I'll eventually find it.
>>
>> On Mon, Sep 25, 2023 at 2:47 PM Julien Villemure-Fréchette via
>> Std-Proposals <std-proposals_at_[hidden]> wrote:
>>
>>> > It seems presenting ideas here without first providing a toy
>>> implementation never gets any traction. "If you can't hack it into
>>> Clang, then nobody cares."
>>>
>>> I certainly do not think that "nobody cares" here, just look at the
>>> tremendous amount of replies got generated, I didn't count the exact
>>> number, but I'm sure we're over 20;
>>> and at least from 5 different people (which includes active committee
>>> members). You actually did get a lot of attention, with a fair amount of
>>> text in responses to your OP,
>>> with detailed explanations as to why people reading this thread actually
>>> judge that what you propose has not enough value to get moving forward.
>>>
>>> Even though you could be right in identifying an unsolved problem, and
>>> if so, that your proposed solution is a good one, people are saying "I'm
>>> not convinced".
>>> (Think of special relativity, nobody took it seriously; it only was
>>> accepted years later, when a rigorous experiment disproved the "Ether
>>> model").
>>> If you fail to convince others, or nobody seem to see the problem you
>>> indicate as something worth fixing, then you must try to prove your
>>> point.
>>> Rephrasing things, or replying by adding more sentences like "things
>>> should be like this" (which already sound opinionated),
>>> or personal anecdotes of the kind "something broke when I did this, but
>>> shouldn't had" (which is hypothetical), then it's not going to be
>>> perceived seriously,
>>> and is no more convincing than repeating the same thing over and over.
>>>
>>> If you want things to go forward, you need to break this
>>> "argument-counterargument" cycle, you need to rigorous proof that
>>> 1) there really is an outstanding problem that needs to be fixed;
>>> 2) and that your solution fixes this problem without bringing in
>>> contradictions or breaking other people's (conforming) code;
>>>
>>> What I mean by a "proof" is, a tangible, reproductible experiment with
>>> consistent observables: describe the steps of the experiment so that
>>> anyone can
>>> run it, and then come up with consistent observations leading to the
>>> same conclusions.
>>>
>>> The sentence "If you can't hack it into Clang, then nobody cares."
>>> sounds more like a catch phrase and can be taken to mean "prove your
>>> point using a rigorous method".
>>> If you someone wants to propose some change in the language's syntax,
>>> starting a topic branch from clang is usually a convenient experimental
>>> setup to support their proposal.
>>> Other methods are not excluded.
>>>
>>> > ... and even in my personal projects I really do not care about that
>>> kind of thing
>>>
>>> The terms "my personal projects" and "I do not care about that kind of
>>> thing" are antithetical to collaborative work --
>>> how about the reciprocal "We don't interact with your personal projects,
>>> so we don't care about the problems you encounter or your personal
>>> specific needs".
>>> A problem is worth solving if it reaches more people than just you;
>>> A solution has value if it improves the language for everyone, in way
>>> consistent with the philosophy of the language.
>>>
>>> BTW, If you don't like that language mechanics to interact with std
>>> library headers, C language has come with the "nice solution" of
>>> introducing
>>> new builtin types with new builtin operator semantics for complex
>>> numbers and booleans. To do so, in a portable way (without breaking
>>> backward compatibility),
>>> type names needed to be in the _Reserved_identifier space, and more
>>> special cases in the formal description of builtin operators (as C
>>> cannot support builtin type's syntax for user defined types).
>>> Does anyone have objection if I'd say `std::new_type` is better than
>>> `_New_type` or that implementing an `operator+` directly in C++ is
>>> better than adding a new special case to the builtin operator `+`
>>> to support the semantic of some `_New_type`? If I recall, supporting
>>> user defined types with syntax similar to builtin types is foundational
>>> in C++.
>>>
>>> Julien V.
>>>
>>> On 2023-09-24 10:38 p.m., Chris Gary via Std-Proposals wrote:
>>> > have to come back to these things later.
>>> --
>>> Std-Proposals mailing list
>>> Std-Proposals_at_[hidden]
>>> https://lists.isocpp.org/mailman/listinfo.cgi/std-proposals
>>>
>>

Received on 2023-09-27 05:05:00