Never seen them used, but I use them in my C++ code when I have to write it to accomplish something else:
https://en.cppreference.com/w/cpp/language/operator_alternat...
They also aren't very consistent between C derived languages. Mostly because C# inherited its versions from VisualBasic, where And is a bitwise operator instead of a short circuiting one.
I didn't mean to imply that they were new features to the language. I meant to imply that they were new style of coding to the manager & coworkers.
Some other features that have burned me from managers & coworkers is `using` to change the visibility of parent class members & functions and CRTP.
... which, frankly, is a very good reason. Don't needlessly change something people are used to.
Why is the blinker control on the left side and the wiper control on the right side? Because that's what people expect.
In aircraft, where it matters a whole lot more that you don't confuse the various levers, the handle of the landing gear lever is shaped like a little wheel and the handle of the flaps lever is shaped like a little wing edge: https://aviation.stackexchange.com/a/22689
Typing "and"/"bitand" seems like the same sort of thing. It's a minor change, but it prevents errors.
'josefx is right in saying[0] these are still primarily hacks around encoding issues.
| meaning | what is | what should have been |
|---------+---------+-----------------------|
| && | and | and |
| &= | and_eq | bitand_eq |
| & | bitand | bitand |
| | | bitor | bitor |
| ~ | compl | bitcompl |
| ! | not | not |
| != | not_eq | not_eq |
| || | or | or |
| |= | or_eq | bitor_eq |
| ^ | xor | bitxor |
| ^= | xor_eq | bitxor_eq |
--All my cars for the last ~15-20 years have had the indicator stalk on the left (i.e. same side as the gear stick), and the windscreen wiper/wash controls on the right, but I suspect that this might be a manufacturer-specific convention.
A quick bit of googled internet wisdom suggest Japanese brands tend to the right, European brands tend to the left, and EU standardisation has settled on left.
Strictly speaking, I would expect the indicator to be activated prior to commencing a manoeuvre, so would expect the two actions to not overlap.
https://news.ycombinator.com/item?id=768151
:)
Unless this reason is causing bugs and security issues.
You can define your own conversion operators to boolean, too, which are very useful for stuff like smart pointers and similar classes that may or may not have a value.
struct A {
std::string value;
explicit operator bool() const {
return this->value.size();
}
};
// ...
A a {};
if (!a) {
// ...
}Moreover, in addition to being less expressive than them, C++'s type system is also weak, in formal sense, by allowing many implicit type conversions - which is one of the issues that I was complaining about. The fact that it "has to be in place due to C not having a boolean type until 1999" doesn't make it any less weak.
The template system and stuff like auto and such are crazy powerful. The Rust typesystem doesn't suffer from the C compatibility baggage and has been designed almost 30 years later, so it is amazing C++ can be so powerful and feel modern despite it being a forty years old language.
I write both Rust and C++ and I have to say, you can do a lot of type safe stuff in C++ too. Most patterns can be backported from Rust and while the ergonomics are not obviously at par, there is still a lot you can do. In C++you can do a crazy amount of metaintrospection at compile time that Rust can only do using procedural macros. Also constexpr and C++20's constinit and consteval are still more powerful than Rust's const.
PL/I and Algol 68 were equally powerfull, or if you research into was being done in Xerox PARC workstations.
If anything, we are 30 years too late for where we could have been if it wasn't for UNIX winning the worstation market.
Integer-like things. C++ char is similar to Rust's u8 or i8 but Rust's char and bool are very deliberately not just integers.
> Does it even support parametrization over function pointers ?
Can you give a clear example ? I think the answer is "No" but I struggled to put together an application for the feature I'm imagining.
You would like to define a type, which is parameterised not by the type of function, but by specific functions? So, for example I can make a foo<sum> and a foo<average> and those are distinct types which presumably internally are using the provided function to behave differently?
Except all the examples I think of come out better with just parametrisation over traits instead. So a clear example from you might illuminate whether this is a sizeable hole or just a difference of philosophy.
sure, but there's an easy mapping from char / bool to integers. C++ supports parametrizing on values of struct type, which is a clear increase in expressive power:
struct foo {
int count;
float init;
};
template<auto arg> struct bar {
float x[arg.count];
bar() {
for(int i = 0; i < arg.count; i++)
x[i] = arg.init;
}
};
constexpr foo f{ .count = 123, .init = 4.56 };
bar<f> b;
> You would like to define a type, which is parameterised not by the type of function, but by specific functions? So, for example I can make a foo<sum> and a foo<average> and those are distinct types which presumably internally are using the provided function to behave differently?yes, it's pretty much the only way in C++ to have zero-cost callbacks / strategy pattern using function pointers since the compiler can directly inline the function pointer everywhere in the type's implementation instead of having to go and read it from some variable at run-time
Because Rust cares a lot about soundness, this gets very tricky for user-defined types. The current idea is that they could be allowed if they derive Eq. What "derive Eq" means in practice is that the compiler is comfortable believing that either you can compare instances of these types to one another with a bit-match or they're composed of types with that property.
You wouldn't be allowed to implement Eq instead for this, because such implementations are safe in Rust, and thus their promises are worthless (Rust has unsafe traits like Allocator, and if you choose to implement those and get it wrong your program has Undefined Behaviour, but by definition that won't happen for a safe trait, even if you deliberately implement it contrary to the specification)
So that rules out floating point numbers because you shouldn't try to compare those to each other with bit matching - NaNs for example.
What does C++ do here? Just YOLO, if you make poor choices the resulting code is nonsense and too bad?
--
We still don't have an example, the constant function pointer thing feels unergonomic to me, I think I'd cook up a Trait representing whatever it is that these functions have in common, and then implement that Trait as necessary to get the same effect by parametrising on the Trait -- but I may very well be missing some affordance your preferred approach has since the equivalent C++ is just to use a Class and finalize the implementations yet you aren't doing that.
You can't parametrize over namespaces (at least not directly) which is an annoying and arbitrary restriction.
It's not a restriction, namespaces are neither types nor values so they would need specific support. Given that you can (ab)use classes with static members as namespaces which are also a type it's simply that nobody cared enough to add support for templating over real namespaces.
Stateless structures are a workaround (so is adl driven by a template parameter), still direct support would be nice.
But that may just be the blub paradox [1] in action
Defining templates on values is very useful, especially in C++ where you can provide template specializations. You can do a whole lot of metaprogramming and compile-time stuff that way.
C++ has a much, MUCH more stronger type system with true generics, value types, const-correctness, compile time reflection and dispatching, ...
In Java, everything is a reference, except when it's not (which is a design mistake that .NET fixed, IMHO). Some stuff in Java is plain "magic", like type erasure and boxing, while C++ might well be drowning in its own sea of utter madness but at least tries to be somewhat consistent (for instance, there are no "magic" types, when you do `int { 3U }` you are "constructing" an integer, when you do `bool x { 33 }` you applying the implicitly defined `bool(int)` constructor from bool. You can define your own conversions, and you can define your own custom types that behave and can be used like built-in ones (see smart pointers, iterators, ...).
Java _seems_ stronger typed because it generally doesn't allow integer promotions and implicit conversions, but these are concepts that are orthogonal to the type system, `bool` and `char` are different, distinct types and if you specialize a template for T<bool>, it won't apply to `char` unless a conversion happens, and if it does so, it is still operating on `bool`, not char - it is constructing a type from another, the fact this happens is simply hidden from you, like Java and boxing (which ironically is an implicit conversion).
The Java delegates pretty much everything to the JVM, and that's reflected in the language design. Java is a simple language that does not do a lot at compile time, relying on runtime facilities to mitigate these shortcomings. See for instance how everything can always decay to a reference to Object, implicitly, everywhere, requiring casts (i.e. runtime assertions) to restore type safety - that's basically a safer `void*`.
1995's Java was clearly too limited, I understand they wanted a fresh start from the ugliness of '90s C++, but they straight removed too much for the sake of simplicity. The current crop of languages, which largely rejected the Java model is kind of a symbol of what went wrong with Java, IMHO.
The fact certain features had been "hacked" on top of the language using what was already there (see generics, boxing, ...), often introducing features that act like "magic", and can't be overridden by the user is bad, and shows how limited the original language was. The same way you can't override operators, you can't define custom boxing rules for your types (mostly because you won't be able to define custom value types until Valhalla is released).
Modern C++ allows you to write safe and solid code using compile-time features and the type system. While stuff like <type_traits>, SFINAE, template metaprogramming and such are definitely not "nice", they are extremely powerful and if used correctly eliminate completely certain issues from ever happening. If you only use smart pointers, references, containers, moves and by-value semantics you won't get crashes from nulls, ever. You won't have memory leaks, and after lots of fighting with the compiler, there's a high chance your code will work straight away (unless you messed up the logic). This is not that far from Rust, as far as my experience goes - I still hope for Rust to mostly replace C++ in the end, but for now C++20 is a very solid substitute and a good choice (and feels much more modern and powerful than Java could or has ever been).
C++ took this quirky base and pushed it (abused it?) in ways that go way beyond what can be considered "sane"; see for instance the bajillion ways operator new() can be redefined, the fact there are at least 10 ways to initialize something, or the fact that features can be _discovered_ and not designed (like CRTP). Still, this wealth of features together with the unwavering (and arguably masochistic) Herculean efforts of the ISO C++ committee to modernize the language make it, by far, one of the most flexible languages around. Every single time I use C++ I discover a new amazing way to do something that had never crossed my mind; call me crazy, but I think this makes programming fun, a detail that is often (wrongly) neglected.