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.
I believe the explanation is:
A && B : compiler will evaluate B only if A==true
A & B : compiler will evaluate both A (safe) and B (unsafe), then perform bitwise operationMany linters and static analysis tools do flag them though as these days it is not considered good practice.
for (auto x : y)
ok &= check(x);
That gets you angry messages from static analysis, so you probably have to write (since there is no corresponding assignment operator) for (auto x : y)
ok = ok && check(x);
...except now you don't actually perform checks after the first failure. That may or may not be intentional (or even confusing - depending on logging). What you'd really need to write to preserve the original logic is for (auto x : y)
ok = check(x) && ok;
Now the `&& ok` is much easier to miss (both in writing and reading) and the intent is much less clear.I really do wonder why the standard doesn't just specify the behavior of bool & bool. Is it just because of holdover from C?
Confused about your question. Did you have a typo? bool & bool is valid in C++.
> What I would like to write in unit tests:
> ok &= check(x);
> That gets you angry messages from static analysis
The setup is that we have a large number of tiny triangles, we cast a ray in a random direction, and we want to detect whether it intersects any of the triangles. The summarized code looks like this:
for (/* all triangles */) {
auto u = calcU(/*...*/);
if (u < 0 || u > 1) {
continue;
}
auto v = calcV(/*...*/);
if (v < 0 || u + v > 1) {
continue;
}
auto dist = calcD(/*...*/);
if (dist < nearest) {
nearest = dist;
}
}
So what's happening?We calculate the (x,y) coordinates of the point where our ray intersects the plane in which the triangle lies.
We convert those (x,y) coordinates to (u,v) coordinates, where the vectors u and v are parallel to two sides of the triangle. (And equal in length.)
In our transformed (u,v) space, determining whether a point lies inside the triangle is very easy. With the origin at the corner of the triangle from which the u and v sides emanate, a point is out of bounds if its u-coordinate lies outside the interval [0, 1], or if its v-coordinate lies outside [0, 1-u]. That's what the ifs after calcU and calcV are checking. When we detect that a point is out of bounds, we move on to the next triangle.
The triangles are small and the ray is random, so the intersection of the ray with the plane will strike at a random point. It is almost always the case that the point will lie outside the triangle. This means that the full conditional (u < 0 || u > 1) will almost always be false.
But the two subconditions u < 0 and u > 1 will each be true 50% of the time. They are individually impossible to predict, which causes branch prediction on the first one, u < 0, to fail about 50% of the time. But they are jointly easy to predict. There is a huge performance gain from switching to (u < 0 | u > 1) -- in this case, 50% of the time we will do the work of calculating whether u > 1 even though we didn't have to. But the reward we get for that extra work is that branch prediction drops from a 50% failure rate (actually 45%) to a very low failure rate.
Including the v-coordinate makes the full check, ((u < 0) | (u > 1) | (v < 0) | (u + v > 1)), even more easy to predict. We've decided to guarantee that we will always do the full amount of work, and most of it is unnecessary. But it's easier to do 2x or more the amount of work and test a condition that fails consistently than to do less work and keep having to clear the instruction pipeline.
Interestingly, the example provided by slavik81 is able to tolerate non-shortcircuiting behavior specifically because no side effects are involved. It's more of a case of "we don't care about the result of this work, but we have to do it anyway, because it's better to do a lot of extra work concurrently than to do only the necessary work sequentially." And doing the extra work is OK because it doesn't have any side effects.
If side effects were involved, you'd be stuck needing to short-circuit despite the fact that it's slower.
There are also areas in cybersecurity where you'd like the time taken for some operation to be insensitive to the input; short-circuiting is bad there too.
The compiler knows that both variants resolve to the same result with no side effects, so it should try to generate whatever code is more efficient. Depending on several factors, that may be machine code that effectively exhibits short circuit behavior or not, for either expression (so even a|b might end up having a short circuit on the lowest level).
This would be different if you, e.g., sprinkle some volatile keywords (which effectively introduces side effects), but then you'd probably still want to take more care and split the expressions up, unless you really don't care about the order of your forced memory accesses (i.e. if I recall correctly, there is no guarantee that a|b will access a first, then b). And that's not even going into barriers.
Of course for that to be a downside someone would have to write tests for security critical code, which obviously did not happen.
Compare `( a() || b() || c() ) ? yes() : no()` to `( a() | b() | c() ) ? yes() : no()`.
In the second case, there are two paths through the code:
a()
b()
c()
yes()
a()
b()
c()
no()
But in the first case, there are more paths: a() /* true */
yes()
a() /* false */
b() /* true */
yes()
a() /* false */
b() /* false */
c() /* true */
yes()
a() /* false */
b() /* false */
c() /* false */
no()
That is because there are fewer branches in the second statement (just one) than there are in the first statement (three).The point of branch coverage is to show when you missed input combinations for your logic. Artificially running all of the branches and then throwing away results just gives you a false sense of security about how much of your logic you covered.
I mean, technically, using bitwise instead of short-circuiting logic doesn't have branches at an assembly code level. But this is pedantry because we're actually trying to check the logic is correct, not that our compiler can correctly translate && into the same Boolean result as &.
"How did we get here?" and "Are we in the right place?" are distinct questions.
if(error1|error2)abort();
Really? I can't recall a single case of this with 'bool' in any codebase in my recent memory. I only see it with integers. And even if it's somehow routine for your codebases with bool (why?!), surely it's not routine inside a conditional expression, so at least they can prohibit it there?
But I like to write as much like assembly as I can in every language. Each line of code should do one and only one thing.
if (foo && foo->bar) {
// whatever
}
An operator that did not short circuit would have undefined behavior because it might dereference a NULL pointer. if(foo) {
if(foo->bar) {
//whatever
}
}
separating out the null check and the actual conditional. More lines of code and more nesting, yes, but, if you're trying to strictly adhere to a one thing/one line principle, you probably don't care.Short-circuit 'or' is a little harder to avoid (if you specifically want the short-circuit behavior), since you'd have to duplicate the code in the body of the if. But that doesn't come up as often IME.
I imagine the compiler spits out pretty similar code either way. It's more just a recognition of my own limitations... I'm a lot less likely to screw up something up, the more explicitly it's written out.
When I was a younger man, I wouldn't have bothered.
Putting the null check in the same line as the usage means that it is much harder to separate them accidentally.
As I wrote in another comment, this could still be the result of a freak accident and not have shown up in testing, though (but since I know nothing about the process or the code there, it could be anything).
There's also linters and other quality control analyzers that will warn on a bitwise operator on booleans.
There is a difference between short-circuiting and non short-circuiting and, hence the distinct operators.
http://www.ada-auth.org/standards/12rm/html/RM-A-1.html#I538...
Of course it makes sense that logical operators wouldn't be applicable to numeric types, but why the reverse?
The inverse is debatable, though. I honestly don't see why someone would do bitwise operations on boolean, unless they really wish to die a slow death from thousand cuts. Integer promotion rules make even the simplest operations a wild mess, and there's nothing in the standard that prevents bool from having a sizeof equal to int (thus further complicating everything).
It might be sensible for newer language revisions to consider deprecating certain operators from being applied to bool, given that there is a strong change that 99% of the time their use is not on purpose, but I guess it depends on how much disruptive such change would turn out to be.
> I honestly don't see why someone would do bitwise operations on boolean
For example, you might want the behaviour shown here of not short-circuiting. Or you might want to create a bitwise function that also allows calling on single-bit (i.e. boolean) values.
In both cases, I think the risk-benefit ratio is way too skewed towards the risk of creating ugly, hard to detect bugs. When I don't want short circuiting from happening, I just assign the RHS of the expression to a variable beforehand; if I want a boolean to become an integer, I make it explicit with a cast.
Sometimes, using certain C and C++ debatable features feels like attempting to shave yourself using a dagger. The fact you could do that doesn't mean you should, or even that it is a good idea.
[1] and in this very thread many people were actually surprised by it!
Also, after seeing how weird JavaScript is in comparison, I have to say that C++ definitely isn't the strangest kid in the block when accounting for weirdness and unintuitive behaviours.
That is a really good question. At some point they decided that true=1 and false=0 but I'm not sure how deep that goes. In particular what does ! actually do in this context?
if (key_data_.has_value() && !key_data_->label().empty())
return key_data_->label();
to be less readable than: if let Some(label) = key_data_ {
label
}
It's notable that people complain Rust adds too many odd punctuation marks, and yet in this example it's C++ with an exclamation mark, question marks, and two different structure member operators (dot and arrow) while Rust just does the pattern match.The simpler solution is to fix this gap in their automated testing. Another one is to configure a linter to error on bitwise operations on boolean values.
> The line should read "if (key_data_.has_value() && !key_data_->label().empty()) {" but instead of "&&"—the C++ version of the "AND" operator—the bad update used a single ampersand, breaking the second half of the conditional statement.
Someone correct me if I'm wrong, but this seems like it wouldn't be possible in a language that didn't conflate boolean values with bitvectors.
Edit: I was wrong[1].
[1] https://news.ycombinator.com/item?id=27923785