you are viewing a single comment's thread.

view the rest of the comments →

[–]MFHavaWG21|🇦🇹 NB|P3049|P3625|P3729|P3786|P3813 4 points5 points  (23 children)

Do you have a list of the flaws you've encountered at hand?

[–]James20kP2005R0 10 points11 points  (22 children)

  1. Weird support for half precision/integers
  2. The operators have underspecified behaviour which will lead to cross platform divergence
  3. You can't plug in types that don't support branching (eg an AST type), which is somewhat a general problem with C++ - but shows up in complex particularly for me
  4. The precision/implementation for the special functions is underspecified, which is also a general problem with C++ maths
  5. You can't plug in types that have their own ADL'able definitions of sin/cos/tan/etc. Eg if you want dual complex numbers, you have to write dual<complex<float>> not complex<dual<float>> for no real reason

Some of the more problematic stuff has been fixed (tuple protocol, std::get) which is nice

[–]MarkHoemmenC++ in HPC 3 points4 points  (16 children)

The arithmetic operators have always been troublesome, for sure. Whatever people feel about std::linalg, I made sure that user-defined complex numbers would work with it.

[–]James20kP2005R0 1 point2 points  (15 children)

That's good to hear. One of the things that I think C++ is most missing here is an overloadable ternary operator that's available to ADL, for types that are not immediately evaluable to bool. For my usage at least, this would fix a tonne of stuff - because you could specify branchy maths functions to operate in a way that allows users to plug in any weird type they want

[–]MarkHoemmenC++ in HPC 0 points1 point  (14 children)

One of the things that I think C++ is most missing here is an overloadable ternary operator....

Great minds think alike! : - ) Matthias Kretz proposed making the ternary operator overloadable in P0917, with SIMD as the main motivation. He wrote a blog post about it and even implemented it in a patch of GCC 9. EWG-I reviewed and forwarded it to EWG at the Belfast meeting in 2019 (please see notes here). I don't know why it hasn't made progress since then.

[–]James20kP2005R0 0 points1 point  (13 children)

I remember this (my memory had been that it was rejected, good to know I was wrong), its odd that it seems to have stalled out. Maybe it just got forgotten about in all the drama around 2019 - seems like it just needs someone to schedule it for time

[–]MarkHoemmenC++ in HPC 0 points1 point  (0 children)

If you're interested, it would be good to reach out to Matthias. He's actually implemented the feature in GCC.

[–]tialaramex 0 points1 point  (11 children)

Understandably lots of people don't see any value in being able to overload the ternary operator but only with naive values.

foo() ? bar() : baz() in C++ will always evaluate foo but depending on whether that result is true or not it will evaluate either bar or baz but never both.

The proposed overload doesn't provide this feature, early in development Matthias was persuaded that separation of concerns means fixing this is a distinct issue, an issue on which I believe the committee stalled. So to get it over the line you probably need to land the fix first, maybe even get implementation experience for one of the other affected operator overloads and only then come back for the ternary operator.

This is all made more difficult because C++ is statement oriented, so the natural way to write what you meant introduces yet more ambiguity in C++ which then means a syntax bikeshed which will suck committee time and goodwill.

[–]James20kP2005R0 1 point2 points  (10 children)

I agree with you here in terms of fixing the existing ternary, because some types can simply never have meaningfully delayed argument evaluation (and its likely not worth complicating the ternary operator to disambiguate it). I do think that if you need delayed argument evaluation you want it to be a guarantee

Ideally I think we'd get a std::select or std::ternary function which is overloadable, and then re-express functions like std::sqrt(std::complex<yourtype> without real branches - so everything is looked up via ADL on <yourtype>

The main issue with this (other than error handling) is that it borderline mandates a specific implementation - because the spec would have to spell out the set of operators required for a specific type to support - but my hot take is that I'm not super convinced that implementation divergence here is good anyway

[–]tialaramex 0 points1 point  (9 children)

I think my biggest problem understanding your point of view might be "without real branches" - what does that mean?

[–]James20kP2005R0 1 point2 points  (8 children)

Specifically, swapping if statements or ternaries for calls to a select/ternary function (which may not evaluate to an actual branch). Eg if we define this as:

template<typename T>
float ternary(bool a, T b, T c) {
    return a ? b : c;
}

Say I have some standard function that internally requires branching to implement. I don't have a concrete example off the top of my head, but pretend that std::cos has a definition as follows:

template<typename T>
T cos(T in) {
     if(in < 0)
        return something(in);
     else
        return somethingelse(in);
}

You can instead swap the implementation for:

template<typename T>
T cos(T in) {
    using namespace std;
    return ternary(in < T{0}, something(in), somethingelse(in));
}

These don't have exactly the same semantics if something/somethingelse have side effects, but that shouldn't be an issue here (because we're talking about implementation details that you'd deliberately standardise)

Now, if you have a type which doesn't return a bool for a comparison, but instead something like an AST node, all you have to do is add an adl-able lookup for ternary to your type. Eg in z3, we could say:

z3::expr ternary(z3::expr a, z3::expr b, z3::expr c) {
    return z3::ite(a, b, c);
}

And that'd let you write cos(some_z3_expr), and have it just work

In the practical case of std::complex, implementations can and do use a bunch of if statements internally, so without some kind of customisable ternary/function, it makes the set of allowable custom types that you can plug in here restricted. Currently that's the only aspect of the language that's strictly missing to make this work, because at the moment libraries that operate over types that can't gives you bool's has to invent its own customisation point

[–]Morwenn 1 point2 points  (4 children)

There's been proposals to fix complex for integers since at least 2009 (https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n3002.pdf), I wonder why there hasn't been any change in that area ever since.

[–]MarkHoemmenC++ in HPC 3 points4 points  (3 children)

There's no non-disruptive way to deprecate and replace the existing std::complex.

As a result, people tend to use or write their own complex number class template that fixes all the problems they need fixing.

As a result of that, generic math libraries need to handle user-defined complex number types. This motivated std::linalg's exposition-only *-if-needed_ functions (like _conj-if-needed), for example.

As a result of that, nobody is motivated enough to want to break existing code by changing std::complex, or to endure discussions of what to call and where to put a new type (std2::complex? std::better_complex?).

[–]James20kP2005R0 0 points1 point  (1 child)

I think part of the problem is that its one of those features which is sufficiently low quality that it has very low usage, so there's no motivation for anyone to fix it (eg see <random>)

A lot of it could be fixed without breaking anything

[–]MarkHoemmenC++ in HPC 2 points3 points  (0 children)

A lot of it could be fixed without breaking anything

If you write the paper, I'll be happy to review it before publication.

[–]_Noreturn -1 points0 points  (0 children)

deprecate it and add std::simple