T̶h̶a̶t̶ d̶o̶e̶s̶n̶'t̶ s̶e̶e̶m̶ c̶o̶r̶r̶e̶c̶t̶:̶ a̶ d̶e̶f̶a̶u̶l̶t̶e̶d̶ c̶o̶n̶s̶t̶r̶u̶c̶t̶o̶r̶ s̶t̶i̶l̶l̶ d̶e̶f̶a̶u̶l̶t̶-̶i̶n̶i̶t̶i̶a̶l̶i̶z̶e̶s̶ t̶h̶e̶ m̶e̶m̶b̶e̶r̶s̶, n̶o̶t̶ v̶a̶l̶u̶e̶ i̶n̶i̶t̶i̶a̶l̶i̶z̶e̶. I̶ d̶o̶n̶'t̶ t̶h̶i̶n̶k̶ t̶h̶e̶r̶e̶ i̶s̶ a̶n̶y̶ d̶i̶f̶f̶e̶r̶e̶n̶c̶e̶ b̶e̶t̶w̶e̶e̶n̶ d̶e̶f̶a̶u̶l̶t̶i̶n̶g̶ i̶n̶l̶i̶n̶e̶ a̶n̶d̶ o̶u̶t̶ o̶f̶ l̶i̶n̶e̶. G̶C̶C̶ s̶e̶e̶m̶s̶ t̶o̶ a̶g̶r̶e̶e̶:̶ h̶t̶t̶p̶s̶:̶//g̶c̶c̶.g̶o̶d̶b̶o̶l̶t̶.o̶r̶g̶/z̶/r̶4̶r̶e̶5̶T̶E̶5̶a̶
edit: I missed that the author is actually value-initializing x!!! The result definitely violates expectations!
Generally, the details of the rules are arcane and sometimes have non-sensical dark corners having been extended and patched up for the last 40 years. But 99.9%[1] of the time you get what you expect.
I big improvement would be making default initialization explicit, and otherwise always value initialize. Explicit value initialization is so common that the very rare times I want default initialization (to avoid expensively zeroing large arrays) I need to write a fat comment. Writing "std::array<int, 100> = void;" (or whatever the syntax would be) would be much better.
[1] I had an extra 9 here... I hedged.
Actually initializing your instances, which is what's expected of every single instantiation, is enough to not experience any problem. This also means that if you want to call a constructor, you need to define it.
This is a case of people trying to be too clever for their own sake, and complaining that that's too much cleverness for them to handle.
Edit: this response is a bit dismissive but honestly my main beef with this article is that its conclusion is just straight up wrong. Do not write your own constructors, do follow the rule of 5/3/0, and if you find yourself needing to hold a const reference, you should look out for whether you’re passing in an rval temporary… none of this is really scary.
> In my humble opinion, here’s the key takeaway: just write your own fucking constructors! You see all that nonsense? Almost completely avoidable if you had just written your own fucking constructors. Don’t let the compiler figure it out for you. You’re the one in control here. Or is it that you think you’re being cute? You just added six instances of undefined behaviour to your company’s codebase, and now twenty Russian hackers are fighting to pwn your app first. Are you stupid? What’s the matter with you? What were you thinking? God.
The problem with C++ and the danger with an article like this is someone might actually follow this advice, instead of eg: the core guidelines.
Every other example is a violation of the core guidelines in some form or another. There is no other problem.
That's my take as well. The blogger clearly went way out of his way to find something to whine about while purposely ignoring a few of the most basic principles and guidelines.
In the meantime, everyone who ever went through a basic tutorial just casually steps over these artificial scenarios.
Your statement may be correct! But it’s certainly not common knowledge in my experience.
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...
std::reference_wrapper still can’t save you from yourself, but it’s better than violating the first link and ending up in this limbo that OP is talking about.
See also: https://youtu.be/YxSg_Gzm-VQ (about 3:30 in)
Nothing else.
https://talesofmytery.blogspot.com/2018/10/harlan-ellison-i-...
> So, here’s the glue between list-initialization and aggregate initialization: if list-initialization is performed on an aggregate, aggregate initialization is performed unless the list has only one argument, of type T or of type derived from T, in which case it performs direct-initialization (or copy-initialization).
The word “unless” is even bold.
We have fancy syntax:
T t{v0};
And we also have: T t{v0, v1};
And so on. But the one-element case does not reliably work like the 2+-element case. And this is in a language that increasingly works toward making it straightforward to create a struct from a parameter pack and has support for variable length array-ish things that one can initialize like this. And the types can, of course, be templated.So you can write your own constructors, and you can initialize a tuple or array with only one element supplied, and you might trip over the wrong constructor being invoked in special cases.
I remember discovering this when C++11 initializer lists were brand new and thinking it was nuts.
It's 15 years out of date now, but also timeless since C++ rarely/never removes old features or behaviours.
I'm hoping something like Herb's C++ syntax 2 will make the language useable for mortals like me.
I think you're whining about something that doesn't pose any problem to anyone with any passing experience in software development.
The examples in the blog post boil down to far-fetched cases devised to trigger corner-cases of a feature where a programming language in exceptional cases auto-generates specific functions when programmers somehow explicitly decided not to do it themselves. The blogger then proceeds to explore edge conditions that lead these exceptional cases to either be enabled or disabled.
In the meantime, be mindful of the fact that this is about a language with a clear design goal of paying only for what you use,and also the widely established rule of 3/rule of 5, which is C++101 and states that when anyone defines one of these special member functions, they should define them all. Why? Because it's C++101 that these special member functions are only generated by the compiler in specific corner cases, and given their exceptional nature the compiler will not generate them automatically if any of the requirements is not met.
Therefore, any programmer who goes through the faintest introduction knows that they have to set the constructors they will use. Is this outlandish?
Also, does it catches anyone by surprise that you need to initialize instances when you instantiate them? Is this too much of a gotcha to justify being called "horrific"?
I think people like you just feel the need to have something to complain about. In the meantime, everyone in the real world happily does real work with them without any fuss.
Thanks for the ad hominem
It’s been a while now, and at least in my experience so far Go and Rusts choice of not having special constructors really simplifies a lot.
Is there anyone that’s had the experience of missing constructors once you swapped away from them?
Unless you want to `ptr::write` individual fields by hand into a `MaybeUninit`, which you can absolutely do mind but that… is not very ergonomic, and requires structs to be specifically opted into this.
Without guaranteed “placement new” that can mean that your 2MB object gets constructed on the stack and copied to the heap. And while Linux defaults to a 4MB stack, Windows defaults to 1MB and will crash your program. Or it might work if the compiler optimizes in your favor.
It's not something you encounter frequently, it can be worked around, and Rust will eventually solve it ergonomically without introducing constructor hell (probably with just a keyword). But finding the best language-level solution isn't straightforward (efforts to fix this for rust are ongoing for 9 years)
This take makes no sense. Think about it: you're saying that not having the compiler do any work for you "really simplifies things a lot". Cool, so you have to explicitly declare and define all constructors. That's ok. But think about it, doesn't C++ already offer you that option from the very start? I mean, you are talking about a feature in C++ that is not mandatory or required, and was added just to prevent those programmers who really really wanted to avoid writing boilerplate code to lean on the compiler in and only in very specific corner cases. If for any reason you want the compiler to do that work for you, you need to be mindful of the specific conditions where you can omit your own member functions. For the rest of the world, they can simply live a normal life and just add them.
How is this complicated?
Complaining that special member functions make obvious things less simple is like complaining that English is not simple jus because you can find complicated words in a dictionary. Yes, you can make it complicated if that's what you want, but there is nothing forcing you to overcomplicate things, is there?
All structs in Rust must be initialized using brace syntax, e.g. `Foo { bar: 1, baz: "" }`. This is commonly encapsulated into static functions (e.g. `Foo::new(1, "")`) that act similarly to constructors, but which are not special in any way compared to other functions. This avoids a lot of the strangeness in C++ that arises from constructors being "special" (can't be named, don't have a return type, use initializer list syntax which is not used anywhere else).
This combined with mandatory move semantics means you also don't have to worry about copy constructors or copy-assignment operators (you opt into copy semantics by deriving from Clone and explicitly calling `.clone()` to create a copy, or deriving from Copy for implicit copy-on-assign) or move constructors and move-assignment operators (all non-Copy assignments are moves by default).
It's actually rather refreshing, and I find myself writing a lot of my C++ code in imitation of the Rust style.
Here are some ways:
- As a junior programmer, it made the language harder to learn. Language complexity increases super-linearly as each new feature has rules of interaction with several existing features
- Although one eventually learns to avoid the anti-features, you cannot control the actions of others. These features meant to help save keystrokes are happily employed every day, producing hard to read code
Particularly when writing library code for other to use or when maintaining large codebases shared by hundreds of engineers, my experience is that complex features in the language end up used by junior engineers or require consideration in API design.
and from what I understand rust constructors are basically the same as java, no?
Rust has functions associated with types which are conventionally used like constructors, but critically the new objects must have all their fields provided all at once, so it is impossible to observe a partially initialised object.
[0] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
[0] except in the more generalised haskell-ish sense that structs or enum variants can be constructed and some forms (“tuple structs” and “tuple variants”) will expose an actual function
Eg:
- Java constructors can return the object before they complete construction, finishing at a later time; this is visible in concurrent code as partially constructed objects
- Java constructors can throw exceptions and return the partially constructed object at the same time, giving you references to broken invalid objects
- Just.. all the things about how calling super constructors and instance methods interleaved with field initialization works and the bazillion ordering rules around that
- Finalizers in general and finalizers on partially constructed objects specifically
I don't in any way claim it's on the same level as C++, but any time I see a Java constructor doing any method calls anymore - whether to instance methods or to super constructors - I know there are dragons
https://codefibershq.com/blog/golang-why-nil-is-not-always-n...
Such as all the constructors that are being added, implicit copy constructor and all the other surprises?
T::T() = default;
> You’d expect the printed result to be 0, right? You poor thing. Alas—it will be garbage. Some things can never be perfect, it seems. Here’s a relevant excerpt from our description of value-initialization:Link: https://consteval.ca/2024/07/03/initialization/#:~:text=You%...
That actually isn't that weird, because it would allow any consumer of your library to change how your library behaves.
Kudos to that author for the great, eye catching title and the in depth detail!
1: https://en.wikipedia.org/wiki/I_Have_No_Mouth,_and_I_Must_Sc...
You have "C with classes" that coexist with the "modern" way, full of smart pointers and functional programming. It is popular in embedded systems, video games, servers, and GUIs (mostly Qt). And if you look at the code, it is as if it was a different language, because the requirements are all very different. Embedded system need low level hardware access, video games are all about performance, servers want safety, and GUIs want flexibility.
There are less awful alternative to C++. For example C on one end of the spectrum and Rust on the other end. But none of them cover every C++ use case.
- There’s at least an ugly library to do it in C++
- There might be support baked directly into the language
- Or you could do it in Lisp, but that would be too easy
Well, it does unstructured imperative, structured imperative, and OOP imperative!
Except if you count template programming, because that one is pure functional, but only runs at compile time.
Literal lol... this is not an argument in favor of C++.
The fact that you can do almost anything IS pretty cool, but without having at least one C++ wizard at hand it can drive you nuts.
If I was starting a new work project with a lot of junior team members, or if I was doing a web project, or a very simple script, fine I’ll use a different language. There can definitely be good reasons not to use C++. But I’m at the point in my expertise that I will default to C++ otherwise. I’m most productive where I am most familiar.
Not disagreeing that C++ is awful in a lot of ways and super difficult though. But I still weirdly like it, personally. I find it a fun challenge/puzzle to work with.
In any case, I think the primary goal of any programming language is to get out of your way and let you tackle more interesting problems related to the problem domain that led you to start writing a program in the first place.
You're basically saying the language gets in the way of solving your problem :)
No other language creates as many problems for programmers as C++.
but only if not knowing how the complicated parts work doesn't create any subtle issues and has reasonable compiler time errors and isn't fundamental needed to write any code
Because then you can use the language without needing to know how exactly that complexity works and if you get it wrong you get a reasonable compiler error. And then decide to either spend some time to learn what you didn't know or you just write the code differently. But in either case you don't have a situation with a unexpected runtime error which should be impossible and where you have no good way to know where to even start looking.
That can't be right ... is it? Things cannot be initialized twice. Isn't it more like "Otherwise, recurse the value-initialization over the bases and members". Then, those that are not classes or arrays get zero-initialized.
It doesn't mean that the lifetime of the object starts twice, or anything weird like that.
You can only initialize once. After it's been initialized you're just assigning values, and that's not what happens during initialization. It's either a misunderstanding on behalf of the author or the words as written are not conveying the correct idea.For good or for ill, I don't really trust anything that long to be something the average human can wrestle down. And when you mix in undefined behavior and the fact it's still used on safety-critical systems... It's a bit horrifying really.
I wonder how much shorter the spec for a radically simplified version of C++ -- say, one that zero-initializes everything, and has no default constructors ("not writing a constructor raises an error" or something) -- would be? 5% 50? 95...?
So their claim that "T t;" will "do nothing" is incorrect.
class T
{
public:
T(int);
};
T t;
Will fail.> Primarily, there are two kinds of initialization of concern: default-initialization and value-initialization. The rules given in the standard look roughly like this:
> * For any type T, T t; performs default-initialization on t as follows: ...
As GP mentions, the article's descriptions of default and value initialization are both incorrect for classes that do not have default constructors, as that code will simply not compile.
I like keeping the rules of the language simple enough that there is never any confusion.
That being said it’s way less complex than C++’s rules and that’s welcomef.
However I can't help but think that maybe I'm just so fortunate to be able to work in a nice code base optimized for developer productivity like this. C++ is really a nice language for experts.
to enforce all fields initialized.
If you care, the linter is there, so this is more of a skill issue.
The Default typeclass (or trait) as seen in Haskell and Rust, is a far better design, as this makes the feature opt-in for data types that truly support them.
"Call J. G. Wentworth, 877 Cash Now!"