Different people wanted "{ } for unified construction" and "{ } for initalizer lists", and we ended up with this mess which made 0 people happy, and ruined unified construction.
Stuff like this terrifies me that I'm signing up for some kafka-esque frustration.
Is there a way out of this (serious question)?
I'm on a codebase of a few hundred kloc and the need for a vector initialized to anything but the default value pretty much never came up. It uses boost, Qt, llvm and two dozen other libraries, yet there is a grand total of eight instantiations of that constructor in it.
When putting a breakpoint, it turns out that all the calls to it I could trigger actually just initialize with the default value statically (e.g. vector<T*> v{x, nullptr};).
Three instances I could grep immediately, all from the same file:
std::vector<std::tuple<std::optional<qreal>, QString, QColor>>
values( // Not list-init
numValues, std::make_tuple(std::nullopt, QString(), QColor()));
(...)
std::vector<size_t> nextTupleIdx(numValues, 0); // Not list-init here!
std::vector<bool> done(numValues, false); // Not list-init here!
I do not doubt a single second that there are 36 ways to write this that are simpler, more efficient and more readable. Yet I would like to hear your suggestion. std::vector<T> vec(numValues);
as vector value-initializes.Personally I prefer an explicit reserve or resize, but still..
Because this is actually still a shift operator, it retains all the precedence of the shift operator even when tired humans think of it as as a clever new stream out operator. So if you're in a situation where shift has precedence, the operator fires, and too bad if that wasn't what you'd intended nor what makes sense to a human reading the code.
Oops.
Some languages let you mint new operators (from some limited combinations of symbols) or allow infix function calls, or by some other means provide for you to nicely extend syntax here, but the overload of unrelated operators was IMNSHO an ongoing disaster whose popularity with programmers is unexplained, like C's decision to treat an array as just the pointer to the front of the array.
But I'm a cranky old man, I also don't like the fact that Rust's + concatenates Strings (this isn't inherent in the language since String isn't a built-in type, in Linux kernel Rust nothing like this exists, because Linus would throw a fit, but in the out-of-box runtime environment Strings implement the Add operator as a concatenate operation).
string s; // initialized (empty)
int i; // NOT initialized (garbage value)
static int i; // initialized (to zero)
char c; // NOT initialized (garbage value)
...I mean, it’s always been a complete mess.The {42,1} is an "initializer list", which was a way of passing a list of things to a constructor.
However, at the same time, something called "uniformed initalization" was added, which used {}, to "clean up" how constructors work.
However, from looking at a piece of code it's impossible to know if it is trying to call "initializer list" or "uniformed inialization". The "initalizer list" wins in this case, when the items are all things you can put in the vector.
In a language that isn't just pretending to be strongly typed, the compiler would notice that char and whatever-integer-type-42-is aren't the same type, and it would reject your program as nonsense.
But that confusion gets much less chance if the language explicitly distinguishes initialising collections so that "I want N of X" is different from "I want N, and X". Compare Rust's vec! macro:
let mut v = vec!['x'; 42]; // NOTE semi-colon. Compiles, vector of 42 x characters
let mut v = vec!['x', 42]; // NOTE comma. Does not compile
let mut v = vec![b'x', 42]; // NOTE comma. Compiles, vector containing 120 (the ASCII code for 'x') and 42 as bytes
let mut v = vec![b'x'; 420]; // NOTE semi-colon. Compiles, vector of 420 bytes with value 120
let mut v = vec![b'x', 420]; // NOTE comma. Does not compile unless you explicitly tell Rust that you mean the low 8-bits of 420 if you do this
I'd actually be enthusiastic about a Clippy warning for the middle one, because it feels like the odds are better than they should be that wasn't what you meant. But on the other hand, the odds of you wanting either possibility are slim, so, not a priority.But what they do with semicolons is pure brain damage. Having different complex semantics encoded purely in semicolons is nuts imho. I don't get how someone could create such mess in an otherwise mostly quite sane language.
Especially as semicolons are an archaic artifact that shouldn't be used for mostly anything in a modern language, imho, as it's just useless noise (besides when writing some well readable one-liners of course ;-)).
It makes code look silly, too, as now initialization looks different from assignment.
int i { a + 2 };
i = a + 4;
int j = a + 2;
j = a + 4; // ah! consistency!The first one will call the initialization constructor, whereas the second form will initialize the object and then apply one form of copy construction, depending on which ones are available (user provided or compiler generated).
It just happens that for int that doesn't matter in practice.
I'm firmly of the opinion that if anyone uses a sane programming language first, they'll absolutely do anything they can to avoid C++.
Corollary being, 1) if C++ is your first language, you'll end up learning all these rules and exceptions to the rules and assume that is just how it is (this is the way I think about Python), and 2) if you use C++ you are aiming for performance that alternative languages cannot provide. I'm glad the gap between alternatives and C++ is narrowing. If I could, I would never write another line of C++ again. Rust and Zig are FAAAARRRR better alternatives to me personally, purely because there's fewer gotchas I have to keep in my head when attempting to do anything with those languages.
One of the gripe I have with C++ is that it has replaced C as the goto language for general purpose libraries, yet it's hard to built bindings for another language (whereas bindings to C are trivial).
It feels impossible to use Rust or Zig, but I have hope I can use Nim. It can compile to C++ and has a simple-ish way of wrapping C++ code. Maybe it would interesting for you too?
For instance, I've been bitten recently by the new "universal" syntax to write variable definitions with only curly braces.
This prints, of course:> 42 2 2