r/cpp • u/DoctorNuu • Dec 19 '23
Obsessed with {} initialization?
I'm on a discord where everyone seems to be obsessed to use brace initialization everywhere.
My motto is: Use things rarely, only when needed, to express that right there the feature does something important.
Personally I would completely advocate against using it for simple stuff likedouble tax{0.09*price};
or just int{0}
probably a hornets nest
38
u/Intrepid-Treacle1033 Dec 19 '23
God old post from a guy that has some cpp experience.
https://herbsutter.com/2013/05/09/gotw-1-solution/
int x{}; "...it always initializes the variable, and is never ambiguous with a function declaration. No vex, no fuss, no muss..."
1
u/guepier Bioinformatican Dec 19 '23
This is a real problem, but it goes away completely when using AA style (
auto x = int();… or,auto x = 0;).6
u/Intrepid-Treacle1033 Dec 19 '23 edited Dec 19 '23
I like auto, its like having a Dog you got when it was a puppy. It follows you wherever.
3
u/jk_tx Dec 19 '23
It has its own issue though, lots of folks still tend to get confused between auto and auto& (or just forget to type the '&'), which can also cause unintended consequences and bugs.
4
u/ThePillsburyPlougher Dec 19 '23
It can also just make code difficult to read.
5
u/DanielMcLaury Dec 20 '23
so can
std::unordered_map<uint64_t, Data>::iterator it = data.find(index);-8
54
u/embeddedsbc Dec 19 '23
The reasoning should be the other way around. Almost always use brace initialization. It avoids automatic narrowing. Use copy assignment for single values or auto. But it's a much better default than the alternatives.
24
u/guepier Bioinformatican Dec 19 '23
On the flip-side, brace initialisation can trigger an unexpected constructor taking an initialiser list. This is arguably more dangerous than narrowing conversions with
(). It definitely leads to plenty of gotchas.I am in the camp (and I know I am not alone!) lamenting the missed opportunity with uniform initialisation. I thought
{}was it, but because of thestd::initializer_listissue, it isn’t. For this reason, I (and others) entirely reverted to()after an initial honeymoon period with{}. Oh, and I use-Wconversion -Werrorso()is more strict than{}.2
u/enigma2728 Dec 20 '23
Well today I learned. Kind of sad; I liked the idea of just being able to use brace initialization everywhere to avoid having to think about all the minutia of initialization.
Is this problem easily demonstrable in godbolt, or have a link to an example of where this becomes a problem?
std::vector doesn't seem to have an issue with braced initializer list. https://godbolt.org/z/scx1rs8s4
Perhaps it is only an issue if there is also a ctor that takes arguments of same type? It does seem like init list gets preferred over specific ctors, from my testing. https://godbolt.org/z/fGsed6o9M
#include <iostream> #include <initializer_list> struct CtorCompetition { CtorCompetition(int x, int y) { std::cout << "2-arg ctor" << std::endl; } //CtorCompetition(int x, int y, int z) CtorCompetition(std::initializer_list<int> list) { std::cout << "init list ctor" << std::endl; } }; int main() { CtorCompetition TwoArgObject{1, 2}; //uses init list, not 2arg CtorCompetition ThreeArgObject{1, 2, 3}; //uses init list //CtorCompetition TwoArgObject(1, 2); //uses 2 arg //CtorCompetition ThreeArgObject(1, 2, 3); //doesn't compile }Program returned: 0 init list ctor init list ctorIs the above what you are referring to?
I feel like perhaps I can still just use braced initializer with the expectation that initializer lists are preferred to specific constructors.
1
u/equeim Dec 22 '23
If the vector's element type can be implicitly constructed from a vector, then
std::vector vec{otherVec}will create a vector with a single element ofotherVec, not copy it.2
u/NilacTheGrim Dec 23 '23
Yeah for actual class types
()initialization is the best way to this day. The ambiguity{}introduces withstd::initializer_listis a huge foot-gun waiting to go off.. and I tend to avoid foot-gun stuff as best I can.1
-13
u/DoctorNuu Dec 19 '23
where does this fear of narrowing come from?
The core guidelines has a lineint y = 7.9; // OK: y becomes 7. Hope for a compiler warning
If your compiler does not offer this warning... well... get a better one13
u/syyyr Dec 19 '23
It should be an error by default. Also, the warning is not enabled by default (I think).
0
u/joeshmoebies Dec 19 '23
/WX /W4 on msvc -Werror on clang/gcc
Problem solved
4
u/syyyr Dec 19 '23
Does it solve the problem it not being the default?
2
u/DanielMcLaury Dec 20 '23
How does the default matter to you? Are you constantly creating new projects by writing a single c++ source file, saving it, and then immediately compiling it directly from the command line?
-1
0
u/joeshmoebies Dec 19 '23
I'm not sure what your point is. If you want narrowing conversion assignments to be errors, you can have them.
As far as the default settings being a problem, apparently not everyone thinks so or Microsoft and the developers of GCC and Clang would change them.
I don't get complaining about something when you can have it exactly how you want.
1
u/syyyr Dec 20 '23
I don't get complaining about something when you can have it exactly how you want.
Yes, with {}.
4
u/jotux Dec 20 '23
https://godbolt.org/z/WG7bcKdcE
Try it on clang and gcc.
Better yet, pick some moderately-sized codebase and change every single variable initialization to uniform initialization syntax and see how many narrowing conversions are happening with no warning. That's what made me switch to 100% '{}'.
5
u/erreur Dec 19 '23
My fear comes from the memory of dealing with data loss in production caused by some accidental narrowing. I used to think this was overblown but now I use brace initialization everywhere and use every flag the compiler supports to disable all implicit conversions of any kind.
14
u/TheFryedMan Dec 19 '23
It's also called uniform initialization. It's supposed to be a single way to initialize everything. It also avoids implicit conversions, which can help avoid bugs. Also, explicitly calling {} will avoid uninitialized values, which can cause undefined behavior. This also avoids something called "the most vexing parse." This issue causes the compiler to think you are calling a function instead of initializing a value. In summary, it avoids a lot of issues and provides a uniform syntax for initialization.
21
u/guepier Bioinformatican Dec 19 '23
It's also called uniform initialization.
And that was a great promise, but unfortunately it is simply a lie, thanks to
std::initializer_list.12
u/shahms Dec 19 '23
It's also called "unicorn" initialization as a play on this misleading moniker and because, much like unicorns, a single initialization syntax which works reliably in C++ does not exist.
1
u/NilacTheGrim Dec 23 '23
It also avoids implicit conversions, which can help avoid bugs.
True, except when it doesn't due to the existence of
std::initialize_list!!-6
2
u/DoctorNuu Dec 21 '23
Finally found a good ally to fight off the downvotes:
https://quuxplusone.github.io/blog/2019/02/18/knightmare-of-initialization/
11
u/aruisdante Dec 19 '23
The core guidelines has a whole section on when and why you should use brace initialization. I think its recommendations are pretty much correct, which is “most of the time”, especially if you don’t use -Wcoversion, -Werror. int a{10} definitely looks worse than int a = 10, but that’s why you should be doing auto a = 10; anyway, so you only state your type once.
11
u/ponchietto Dec 19 '23
I don't get why auto a = 10; would be better thant int a = 10;
I prefer being explicit rather that have the reader (including myself) deduce the type (extra effort) and very easily use the wrong type (10 vs 10.0 vs 10.0f vs 10U).
The compiler will complain if I am not consistent.8
u/aruisdante Dec 19 '23 edited Dec 19 '23
The compiler will in fact not complain if you use it wrong without
Wconversion, that’s the point the guidelines are talking about.int narrowing = 4294967296; unsigned int signed_loss = -1; int float_conversion = 11.1; int arbitrary_conversion = foo(); // foo returns double;Those will all compile without warning if you don’t have
Wconversionenabled. Whereas if you brace initialize them, the compiler will warn even without-Wconversion.Now of course you should just have
Wconversionturned on, and then life is easier, and you can use=assignment safely. One of the constant frustrations in my day job is that AutoSAR and MISRA both ban=initialization anyway (and, thanks to AutoSAR’s terrible writing, also it also technically bansautocompletely, as it sates=initialization must be used forautoimmediately after stating brace initialization must be used for all variables). Thankfully MISRA2023 takes a more enlightened view here.In terms of being explicit with what type you want: the
auto a =form is explicit. You want the type, and exactly the type, to the right of the assignment. For example, if I see:int a = 11.1;How do I know which was intended? Did the author mean for that literal to be
11? Did they mean for the type ofato befloatordouble?Now you can definitely go overboard here, but the general guidance of “only state your types once” serves pretty well.
1
u/Nobody_1707 Dec 23 '23
One nice oddity of concepts is that you can write
std::same_as<int> auto i = x;and it will prevent any implicit conversions whatsoever.0
u/ponchietto Dec 19 '23
I have nothing against braces, I was just using the example above, my problem is auto (in this case).
"How do I know which was intended? Did the author mean for that literal to be 11?" The author made a mistake, probably on the right, I don't see the point, though.
It is just far easier for human beings to read the type in front of the variable rather than deduce it from the right.
Moreover you might not have a number, you could have a constant, a function or an expression on the right, when does it becames non obvious enough?4
u/bert8128 Dec 19 '23
If you specify the type in the right, don’t specify it on the left, unless you want it to be different.
1
u/shahms Dec 19 '23
And Abseil has a whole section on the pitfalls of brace initialization: https://abseil.io/tips/88
Initialization C++ is absurdly convoluted and subtle. Braced initialization solves some problems with the other ways of doing it, but introduces a whole raft of its own problems. There is no single "always good" way of performing initialization in C++, sadly, and the core guidelines do a serious detriment here by pretending that there is.
5
u/aruisdante Dec 19 '23
Yes, and for the most part I agree with Titus too, but he’s concerned more with brace vs paren initialization than brace vs
=initialization. I’ve in fact used that very article to try and overturn silly “always use {} initialization” rules at 2 different companies in the automotive space.I think that’s the biggest issue with all of the guidelines, they don’t distinguish between separate concerns:
I don’t want implicit conversions for arithmetic types
I don’t want implicit conversions for structures which have non-explicit constructors, or which consume arithmetic types.
I have
Wconversionturned on, should I care?The last one can be particularly subtle.
Wconversionignores implicit conversions that happen inside the stdlib, because it has to as the stdlib is full of them. There are certain types which have templated constructors (like, say,std::optional), and so if you parens-construct them in a non CTAD context but which would result in a conversion that should be caught, it will not happen: from the perspective of Wconversion the ctor that was called was the converting constructor, which consumes the actual type, and then inside the stdlib a narrowing happens, but that’s ignored. If you brace initialize them on the other hand, the implicit conversion is not allowed, and Wconversion fires.You’re absolutely right that the whole subject is a mess. Unfortunately teaching people all the ins and outs of what to do isn’t a reliable thing to do at scale. So most safety critical coding standards pick one, and unfortunately they tend to pick the most restrictive one even if it introduces a host of other, potentially much worse classes of bugs. The core guidelines doesn’t really have the same excuse, but it’s guideline also is poked through all kinds exceptions, and it’s also a prefer, not a must. And I agree with the guideline’s suggestion that brace initialization should be preferred unless there is a good reason to not use it. I do not agree with AutoSAR’s requirement that it must be used.
1
u/shahms Dec 19 '23
The tip specifically discusses the differences between all three forms of initialization, including
=and says to prefer copy initialization.1
u/Intrepid-Treacle1033 Dec 19 '23
"And" ?
Abseil is an article about an article. In the bottom of Abseil article it states "Perhaps the best overall description of the issue is Herb Sutter’s GotW post. (link)"
And i agree, Herbs article is good.
1
u/shahms Dec 19 '23
Wat. The Abseil tip includes a description of the issues and recommendations. It then suggests reading Herb's article as a follow-up for additional context.
2
u/Intrepid-Treacle1033 Dec 19 '23
In my effort to go to the bottom and really learn initialization i read among others the Absail and did not get it, but there i got the Herb link and that one nailed it for me. So for me the Absail was just an distraction hence my article about an article comment. But ok reading it again i see a angle, and i did not really mean article about an article as a negative thing.
-4
-2
18
5
u/mredding Dec 19 '23
Obsessed with {} initialization?
Obsessed with not? What about narrowing conversions?
The standard committee largely can't and won't break old code. They can't add a constraint to assignment, but they can add a new initialization. All they can do is provide a migration path forward.
All the new features are meant to fix problems in the past. Your conservative approach is benefiting you... how?
And this is how standards and practices stagnate. You have to look at the present and be ready for the future. We need a language and a standard that meets the needs of today. You're on r/cpp so you must be aware of the outrage that the NSA report stirred.
And they're not even wrong. It's not that C++ isn't safe, it's because we have solutions to problems no one is bothering to use. Don't avoid C++ and favor Rust for a lack of technology, but for the people. You just can't find people who can be bothered to write high quality code.
Your stated motto only tells me that you don't use new things because they're new - not because they're bad. You've complained, but you haven't given a credible reason to voice a complaint. There's no technical reason not to use brace initialization - none that you established. Where's the flaw? Where's the fault? What gives your grievance merit? What guideline would you suggest that isn't new = bad?
9
u/android_queen Dec 19 '23
My motto is: Use things rarely, only when needed, to express that right there the feature does something important.
I lean only slightly towards using brace initialization, but oddly, this made me want to use it more. Using things rarely sounds like a good way to make them unfamiliar and confusing. Consistency is much better for communicating your intent. What does it mean to only use something when “needed”? There are relatively few things that can only be handled on one way.
4
u/lukaasm Game/Engine/Tools Developer Dec 22 '23
It prevents more bugs for me than causes issues, and overall is net positive.
zero initializes structs/values
allows for 'custom' struct fields initialization for aggregates
prevents narrowing conversions
prevents function calls
But I am also not using initializer lists, so it won't bite me :)
2
u/NilacTheGrim Dec 23 '23
I am also not using initializer lists, so it won't bite me :)
I don't typically use it either but some types in my code rely on libs that have introduced
std::initializer_listc'tors in between releases and I had at least 1 bug in my past where a new wildstd::initializer_listc'tor appeared between lib versions and produced subtle bugs.My rule of thumb is if you intend to call a c'tor on initialization (say because it's real non-trivial class type), then use
().
3
u/islandyokel Dec 20 '23
Here is some info about it in the core guidelines: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Res-list
It describes there where and why of the preferences
6
u/EdwinYZW Dec 19 '23
I would do: const auto tax = 0.09 * price;
-16
u/snowflake_pl Dec 19 '23
Why not static constexpr? 🙂
15
u/yu_yaki Dec 19 '23
because price might not be constexpr? Why static though?
-9
u/snowflake_pl Dec 19 '23
Maybe static doesn't make a difference for a small variable but was it large, giving it static storage ensures compile time initialization and prevents unnecessarily copying the data to stack
4
u/yu_yaki Dec 19 '23
It's still irrelevant to the specific case that you commented under. I was wondering if you had some more to say, but oh well. I do get your point though.
2
u/android_queen Dec 19 '23
Yes, but it does potentially bloat your static storage, and you don’t have enough information from this single line of code to know whether it would need to be on the stack anyway (which is not unlikely). Static isn’t one of those things that is automatically by default the better choice.
1
u/snowflake_pl Dec 19 '23
and by no means I am recomending it. Wanted to have a discussion which is exactly what happens here
1
u/android_queen Dec 19 '23
I guess it just seemed odd to me to ask “why not static” when I can see no reason why it should be.
1
u/snowflake_pl Dec 19 '23
I mean there is huge lack of context both in the original post and in my question so I understand the confusion.
3
u/_curious_george__ Dec 19 '23
Doesn’t necessarily make sense in practice.
Static storage is going to pollute the cache. Which will often be far worse than copying some trivial variable to the stack.
0
u/snowflake_pl Dec 19 '23
For optimized builds and trivial types static and automatic will be equivalent with variables completely compiled out. For bigger entities you are right, cache miss will most likely be more pricy than copy to stack. There is a threshold where copy becomes worse than cache miss but it's pretty much impossible to determine without precise measurements and even then the result will be tied to hw
2
u/Droid33 Dec 19 '23
I tried it for a while but it just makes the code very unreadable in my experience. Wish that weren't so.
2
u/gracicot Dec 20 '23
Consider yourself lucky, I advocate (and enforce) for auto const tax = double{0.09*price}. Btw, never contain money using floating point. Money is always a integer type containing pennies. You'll thank me later.
2
Dec 22 '23
I quite prefer it because it looks prettier than int i=0;
I don’t know if it actually changes something about compilation or performance . It’s fun to use it!
2
1
u/Drakonluke Dec 20 '23
Basically because Bjarne Stroustrup told us that list initialization (curly braces) is better.
Since I don't want to copy from his book by hand, I see that someone already did here:
1
1
u/13steinj Dec 22 '23
There's a few general rules of thumb which end up promoting this:
braces for aggregates, parenthesis for classes, since constructors do work. I honestly never bought this approach. There are plenty of edge cases where your aggregate construction also does work, and aggregates can now be constructed with parenthesis.
assignment can do work / have a different meaning, difficult to catch initialization or assignment so better not to leave room for the question
parenthesis make it hard to tell if it's a function or a constructor, and sometimes this matters
uniform initialization is powerful and lets you do tricks on template-arg erased functions that are bound to a type without having to be overly explicit.
default initialization can lead to unexpected uninitialized variables
As a result of a mix of the above, I'm in the "braces over parenthesis, be explicit and use them" camp as well as the "when using assignment use uniform initialization" camp.
1
u/NilacTheGrim Dec 23 '23
I agree, I think it's totally over-used, especially for obvious stuff like PoT types or for calling c'tors... I actually prefer Foo f(bar, baz) notation for initializing via c'tor because it is symmetrical / visually the same as any other function call. Also there is no ambiguity between that and initializer lists... so there's that.
Thay being said I do like init-to-0 using {}, e.g. int i{}; as opposed to int i = 0; simply because the former is more compact.
1
u/NilacTheGrim Dec 23 '23
I agree with you. May I present this as evidence that {} for non-trivial classes is dangerous :
std::vector<char> vc {42, 'x'}; <-- tell me, what is the size of vc here? Is it 42 or 2?
If you aren't sure, then you need to stop using {} and go back to () for non-trivial class types right now.
-2
Dec 19 '23
Prefer {} initialization over alternatives unless you have a strong reason not to. copy past from stackoverflow.
11
u/guepier Bioinformatican Dec 19 '23
And for completeness, copy paste from a comment under that answer, showing why the answer is problematic:
braced initialization becomes a complete mess when you have types with a ctor accepting a
std::initializer_list. […]A(5,4)andA{5,4}can call completely different functions, and this is an important thing to know. It can even result in calls that seem unintuitive. Saying that you should prefer{}by default will lead to people misunderstanding what's going on. This isn't your fault, though. I personally think it's an extremely poorly thought out feature.2
Dec 19 '23
I just started learning cpp so I don't know, but learncpp also recommend to prefer brace initialization over copy and direct initialization.
2
u/guepier Bioinformatican Dec 19 '23
… And does it give reasons?
Anyway, I don’t know “learncpp” specifically, but be aware that the majority of C++ learning resources are of terrible quality according to many C++ experts. Don’t take everything you read (online or in books) at face value.
If you are looking for high-quality C++ learning resources, look at the curated list of C++ books.
6
u/ponchietto Dec 19 '23
When should I vote down?
Downvote answers that are incorrect or don't provide sufficient information to be useful in answering the question. Some answers may not attempt to answer the question at all, and should be flagged.Copy past from stackoverflow (https://stackoverflow.com/help/privileges/vote-down).
0
0
-6
u/vI--_--Iv Dec 19 '23
{} requires 4 keystrokes.
= requires 1 keystroke.
The choice is obvious.
5
u/guepier Bioinformatican Dec 19 '23 edited Dec 19 '23
Counting keystrokes is a pretty bad metric for syntax superiority, but your count is also wrong: “
{}” requires three keystrokes on a US-English keyboard, as well as many (most? all?) other keyboards:Shift,{,}. (And the two adjacent keys can be typed in a much quicker succession than two arbitrary keys, and the first{and the shift key can be pressed simultaneously, so in reality typing “{}” is much faster than three average keystrokes or even faster than two average keystrokes.)2
1
u/fdwr fdwr@github 🔍 Dec 20 '23
your count is also wrong
u/vI--_--Iv left out the value in his example (unsure if intentional or oversight),
{5}vs=5. Typing{5}requires 4 keystrokes for the curly braces portion because you must release Shift between braces, as you'd otherwise end up with{%}.
0
0
u/jones77 Dec 19 '23
I have like 3 tabs open with information about this I need to read. :-)
Hrm, looks like I lost a tab...
50
u/jedwardsol const & Dec 19 '23
What do you advocate using instead, and why is that better?