Live data from Hacker News

Help me sort out the meaning of “{}” as a constructor argument

scottmeyers.blogspot.com

131–140 of 181 posts

Re: Help me sort out the meaning of “{}” as a constructor argument

#131

Earlier quoted context omitted.

> Undefined behavior is not specified > unspecified is stronger than undefined because it gives requirements. I do not understand what you are saying.

"unspecified behavior" in C++ is a formal term denoting a situation in which requirements are in fact given. There are multiple possible requirements, and the implementation chooses which apply, without having to document the choice. So in fact something is specified, just not entirely. "undefined behavior" refers to situations in which no requirements apply at all. (Truly unspecified in the regular sense of the word…

Thanks, I understand your usage now.

Unspecified means the implementation has some leeway.

Undefined means the implementation can do anything.

Re: Help me sort out the meaning of “{}” as a constructor argument

#132

Can someone explain how this "universal" syntax is supposed to be better than the old syntax?

T() had different meaninga depending on T and the context. It could be value initialisation, a cast, a function declaration(!). Also, when used with arguments, you couldn't use it to uniformly initialize aggregates and non aggregates, which is important in generic code.

The T{} syntax can be either a constructor call or aggregate initialization and never a cast or function definition.

Re: Help me sort out the meaning of “{}” as a constructor argument

#133

Earlier quoted context omitted.

"unspecified behavior" in C++ is a formal term denoting a situation in which requirements are in fact given. There are multiple possible requirements, and the implementation chooses which apply, without having to document the choice. So in fact something is specified, just not entirely. "undefined behavior" refers to situations in which no requirements apply at all. (Truly unspecified in the regular sense of the word…

Thanks, I understand your usage now. Unspecified means the implementation has some leeway. Undefined means the implementation can do anything.

Yes; as in "unspecified which choice".

Re: Help me sort out the meaning of “{}” as a constructor argument

#134

I've been programming professionally for over 20 years, most of it in C++. I really like developing fast code, and C++ used to be my favorite tool for the job. But the language complexity gets more insane with every revision. It's now at the point where Scott Freakin' Meyers can't figure out the interpretation of a short little chunk of code. I'm now actively looking for gigs in which I can develop high-performance c…

Any sense of where the C++ refugees are heading? Rust? Go?

Honestly for me… C++. C++ but with restricted and consistent feature usage at every level of the stack. This means no stdlib, no stl, no boost. Writing your own pared-down standard library is actually not that hard if you make sure none of the calling code hits edge cases. 90% of boost just makes sure it's robust in the face of anything (classes that are movable but not assignable, initializer list constructors that behave differently, classes that overload unary &, etc… just weird stuff). Honestly the only reason I do this is simply because I like writing fast code and C++ is a good tool to do that. It would take me longer to become equally proficient in Rust, D, etc, than to just write 100% dependency-free C++. I mostly work on programming language implementation though, so libraries are less of an issue than in other possible situations.

D has been catching my attention a lot though. I just wish it had something kind of like C++'s move semantics, to make implementing owned pointers (C++ unique_ptr, Rust Box, etc) possible. Maybe it does and I just haven't seen it used yet. The other problem I have with D is that most libraries seem to rely on the GC.

Re: Help me sort out the meaning of “{}” as a constructor argument

#135
post #27

It seems to me C/C++ has always had problems. K&R C had weird stuff like char pointers used to point to anything. And modern C++ has odd complexities like what Scott's article illustrates. Was there ever a Goldilocks moment when C was just right ?

I'm struggling with this too... I'm implementing a shell, and on the one hand I've been looking at ancient C code in bash, dash, zsh, mksh, etc. That's clearly not the right way to do it, with raw strings, pointers, and globals everywhere. It's ridiculously verbose and error prone. All recursive parsers in C seem to use setlongjmp for error handling. They have weird memory management with stack allocated strings and…

I'm not quite sure what you mean by 'goldilocks moment', but I think C++11 was it. Ever since then, the language has had a nice balance of high level abstractions at low level performance. (Theoretically it actually got faster due to move semantics)

Re: Help me sort out the meaning of “{}” as a constructor argument

#136
post #85

Earlier quoted context omitted.

Fun like this: http://www.flotsam.nl/dispatch-periodic-table.html When I was a teenager, it was fun to do that in C++ until I realized everybody thought it was fun but their version of fun wasn't compatible with my version of fun.

If all you found was an old API from half a decade ago that doesn't even exist anymore, than I think that's a pretty good case for Scala.

That's irrelevant. Scala simply allows your coworker to go "off the rails", employing their creativity even to language constructs, and you either spend time to learn their way of thinking, set firm rules about what is allowed, or you are simply out of luck, like often with C++.

Re: Help me sort out the meaning of “{}” as a constructor argument

#137
post #135
post #27

Earlier quoted context omitted.

I'm struggling with this too... I'm implementing a shell, and on the one hand I've been looking at ancient C code in bash, dash, zsh, mksh, etc. That's clearly not the right way to do it, with raw strings, pointers, and globals everywhere. It's ridiculously verbose and error prone. All recursive parsers in C seem to use setlongjmp for error handling. They have weird memory management with stack allocated strings and…

I'm not quite sure what you mean by 'goldilocks moment', but I think C++11 was it. Ever since then, the language has had a nice balance of high level abstractions at low level performance. (Theoretically it actually got faster due to move semantics)

The parent was asking the goldilocks question -- was there ever a point where C++ was not too complex like modern C++, e.g. this {} issue, but had enough features (contrast with C, which doesn't have enough features for many applications). I don't think there was such a moment.

Apparently move constructors were a de-optimization in the case of std::vector. As far as I remember, it's related to iterator invalidation and move semantics making small-size optimization impossible (e.g. inlining small vectors into the object itself rather than having another pointer and heap allocation). I'm pretty sure it's in this video:

https://www.youtube.com/watch?v=vElZc6zSIXM

Although it's debatable how big a deal this is, it's a good example of the point. People like "C with classes" because they can read the source and kind of figure out what code is generated. They can reason about locality.

Although I don't consider myself an expert C++ programmer, if I can't reason about the consequences of move constructors on std::vector, which is a fundamental and relatively simple data structure, then the language is already getting into "surprising" territory. I would have never figured out that de-optimization unless I heard it in a video.

On a related note, I wish that return value optimization had better syntax -- that would make move semantics unnecessary in a lot of cases.

On the other hand, reasoning about the generated code is already a lost cause because assembly code is so complicated now. We now have to live in a world where we can never fully understand our tools.

Re: Help me sort out the meaning of “{}” as a constructor argument

#138

The answer to the problem can be found in the comments, but not in a single place: X {{}}; will: for T = DefCtor call the initializer_list constructor with a single default constructed element. The outer brakets are for the initalizer list and the inner ones are for the DefCtor constructor. The brackets for T constructor itself are allowed to be elided. for T = NoDefCtor, the previous overload resolution is not valid…

My god c++ is atrocious.

I would not go so far to call it atrocious, but it has demonstrated how a collection of rules, each (for the most part) well thought out, can lead to a combinatorial explosion of complexity, together with some highly unintuitive corner cases. The strong desire for backwards compatibility all the way to C, together with some expedient principle-breaking, has made things more difficult. All of this has been thoroughly investigated and well-documented by Mr Meyers, and I would like to thank him for all his books and articles on the language.

Re: Help me sort out the meaning of “{}” as a constructor argument

#139
post #92

Earlier quoted context omitted.

I agree. And for myself at least, I could also say: I'm not particularly fond of fully OOP languages, but they have some nice ideas that I've been enjoying using. If a thing I want to make is a stateless process, modeling it as a function seems nicest. If that function can be truly a function (i.e. pure), that's best. If it can be semi-pure, in the sense that its only access to mutation is via its arguments (e.g. it…

FP and OO are completely orthogonal in my eyes. I can utilise immutable data with referential transparency just as well with Objects as I can with Modules + Structs, but only the latter is called "FP". I think because Java and C++ championed a very impure OO, based around C-like imperative semantics, people regard huge bundles of mutable state as canonical OO. To me that's not the big idea, the big idea is Objects vs…

Popular is not always (or even often???) the best way to go ;-) Being on the fringe usually means you've thought about it more than most people.

I'm an OO guy who has been moving more and more in the FP direction. I look at it a bit differently that you, but basically have the same conclusion (I think).

Rather than saying OO and FP are orthogonal, I tend to feel that OO and FP are essentially the same, but that popular OOP misses the point completely.

If I have a struct and a set of functions, where the first argument of the function is always the struct, how is that different than having an object with a set of methods? It might (or might not) be implemented under the hood differently, but whether I write the object on the left, or as the first parameter, it's all the same.

With respect to mutability, most FP languages don't allow mutable data structures. I can likewise restrict myself to immutable data structures in OO languages. Most people don't. Unless you have a specific performance issue in mind, I think this is usually a mistake. I think the fact that mutable data structures are popular, is not equivalent to saying the OO encourages mutable data structures. It's equivalently bad in both camps (which I suppose is the meaning of "orthogonal" ;-) ). Immutability has memory management considerations which I will discuss in a minute.

WRT polymorphism, with OO languages, ad-hoc polymorphism (operator overloading) is essential, while parametric polymorphism (generics) is optional. With FP, it tends to be exactly the opposite. But if you consider that both ad-hoc polymorphism and parametric polymorphism are useful tools in both approaches, I think it's kind of a moot point.

The rest of it boils down to coupling and cohesion. In OO, you have encapsulation which forces you to reduce your coupling in certain ways. You tend to have cohesive code in that all functions dealing with a particular data structure are grouped with that data structure. In FP, you have cohesive code in that all your functions doing a particular operation are grouped together. Coupling is not strictly enforced, but writers of good FP code enforce it themselves. But it's not like you can't do both styles in both systems. It's just that the language implementations make one style easier than the other.

The only place that I see which is very different is in garbage collection. If you are using immutable data structures, it is hard to know when to deallocate the memory. So you need some kind of garbage collection (even if it is just reference counting). Having it built in to the language makes it very convenient, but it is hardly a show stopper if you have to write your own (or use a library).

With C++ there is an idiom (which I still think is a good one) where you try to only allocate memory on the stack. Everything is a local variable and you send those local variables to functions that fill in the data for you. That way you can explicitly understand the lifetime of every data structure in the system. I've done a lot of embedded code where I needed to keep track of every byte of code and to know exactly where it is allocated and deallocated. Techniques like this help, but it is obviously not immutable. I don't know how to do this kind of thing with traditional FP languages.

But really, it's only the very last thing that strikes me as being particularly different. The rest is syntax and how much typing I need to do. It's more that the language designers have idioms that they prefer and make those idioms easier. FP and OO seem to share the same pool of idioms, though.

Re: Help me sort out the meaning of “{}” as a constructor argument

#140
post #75

Earlier quoted context omitted.

Those languages all have heavy runtime environments; if a segfault occurs it's an interpreter bug. Whether the mistakes you can make are a result of "undefined behavior" is a different question whether from the consequences of a mistake are an abrupt termination, or an abrupt termination with a helpful error message.

This is how it works in Ruby. In the CRuby/MRI interpretter when a segfault happens it calls the #to_s method on the current execution context, to get the some memory address and technical details. Ruby happens to be flexible enough that you can override that method. Then you can call whatever you want and you have effectly "recovered" from a segfault. The parser is totally in an inconsistent state and could crash at…

[deleted]
Post reply on HN