Hacker News new | ask | show | jobs
by Trollmann 1790 days ago
You can use `and` and its siblings instead of && and similar in C++11. Most people here, and in other boards, will try to convince you that it hurts readability because 'that‘s how we always did it' (read I‘m used to it and don‘t like change).
5 comments

I was surprised to read that `and`, `and_eq`, `xor`, etc are all supported "secondary/alternative operators"

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...

I used to use them. Then I was burned by some Very Opinionated managers and coworkers who didn't like seeing new things. It wasn't a hill I was willing to die on; there's more important things to argue about in C++.
They have been around since the first standard and basically only exist as a hack around an ancient non ascii compatible encoding. So unless you got burned in 1995 nothing about them was new.

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.

> * So unless you got burned in 1995 nothing about them was new.*

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.

Development is a massive cargo cult
I use them, some of them anyway. I find them to be more readable (in particular’not’ instead of ‘!’). Also it means that I can reserve ‘&&’ for rvalue references.
While it is true that you can use `and` in C++11, that's a bit of an understatement: these keywords have in fact been present since the very first ISO C++ standard C++98!
Note however that MSVC does not support these by default. You need to #include <ciso646> before their use, or you need to pass the `/permissive-` compiler option.
You should be using /permissive- anyway unless you are dealing with a legacy codebase that breaks with that.
TIL! Always thought this was a C++11 feature. Then this argument against the 'new' keywords is even weaker.
> that‘s how we always did it

... 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.

On the other hand, if it were actually the case that people kept turning on the wipers instead of signaling their turns, it would be a sign that we should figure out how to make these two different operations not use symmetrical levers, and that it would be okay to change people's expectations because those expectations weren't very firm.

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.

Wish it was consistent, though.

'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             |

--

[0] - https://news.ycombinator.com/item?id=27928276

No it's because this way your left hand only deals with blinker, and your right one wih the gear shift. Separation of concerns. It's reversed in the UK of course.
The gear stick may be on the driver's left in the UK, but the indicator and windscreen wiper/wash stalks do not necessarily follow.

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.

Also - super old reference:

https://news.ycombinator.com/item?id=768151

:)

Fyi it does in fact change depending on the car manufacturer.
> ... which, frankly, is a very good reason. Don't needlessly change something people are used to.

Unless this reason is causing bugs and security issues.

I am also of the opinion that `and` is more readable than `&&` (and isn't as easy to typo in a catastrophic way) - although my main point was about the weaker type system.
C++ definitely hasn't a weaker type system than "newer" languages like Java - if any, it is much more richer and complex than most languages out there. What's happening here is a type conversion that has to be in place due to C not having a boolean type until 1999. C++ attempts to construct a boolean from the argument of an `if()`, and given that bool can be constructed from int, the conversion succeedes.

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) { 
     // ...
  }
"Not as weak as Java" is not a very interesting benchmark, as Java also has a poor type system. C++'s type system is pitiable relative to those of Rust, Haskell, OCaml, and SML.

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 fact implicit conversions exist doesn't really weaken the type system, the two are different concepts. If you define an overload or a type specialization on both the compiler will call the right version without any ambiguity. You can even define your own implicit conversions itself, and sometimes they have their uses, like when you need to wrap values in proxies but you still don't want the user to actually have to know that.

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.

It is nothing to be amazed, other than we are finally able to enjoy such type systems in mainstream languages.

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.

The type system in C++ is pretty strong actually, it just has too many implicit conversions. It's not like the compiler doesn't know that this value is not a bool.
Which of "Rust, Haskell (core Haskell), OCaml, and SML" is able to parametrize types on values, à la template<auto> ?
It only supports integers so far, which c++ could do pretty much since before it was even standardized 25 years ago. Doesn't seem to support as wide of a type menagerie as C++20's NTTP. Does it even support parametrization over function pointers ?
In extremely limited form. That should change in the future, though.
Much more usefully, in C++ you can also parametrize over templates, which I think you still can't in rust.

You can't parametrize over namespaces (at least not directly) which is an annoying and arbitrary restriction.

> 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.

For those languages, I don't really see a need to paremtrize types on values. Because of the AMAZING generics support.

But that may just be the blub paradox [1] in action

[1] https://wiki.c2.com/?BlubParadox

Well the fact you couldn't reasonably use arrays without Generic Const hit stable in Rust 1.51 says otherwise.

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.

I don't think weak has a formal sense in any formal sense.
Java has a poor type system, but not as poor as that of C++.
This is uttely false. I desume you haven't used C++ in the last 10 years, or at least you didn't delve deep enough into it to really understand how powerful (while bonkers) the C++ type system is.

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).

All versions of C++ inherit type system from C and keep it for backward compatibility. Indeed you can opt into a stronger dialect with right compiler flags, but this is used as widely as Prolog, because the rest of ecosystem uses an incompatible dialect and will fight against you. C++20 is a substitute of C++17, not of other languages.
I just wish there would be a -fsafe compiler flag instead of having to deal with all the sanitizers and debug mode flags, but it is what it is.
you can even use it for rvalues!