Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
The Sad Truth About C++ Copy Elision (wolchok.org)
70 points by chmaynard on April 3, 2021 | hide | past | favorite | 81 comments


> If compilers were to unpredictably remove copies, and thus remove pairs of copy/move constructor & destructor calls, they might break your code.

But they are permitted to do exactly this. This is one of the unusual things about copy-elision in C++. The standard says that under certain circumstances, the compiler is permitted (but not required) to elide a copy operation even if this causes a change in observable program behaviour.

* https://en.wikipedia.org/wiki/Copy_elision

* https://en.cppreference.com/w/cpp/language/copy_elision (the article referenced by the post)


They are permitted in certain cases, not in others. It is proposed to widen the former list for C++23.

All this is an argument for "rule of zero": write your classes in a way that the default copy and move constructors and destructor do the right thing. That gives the compiler latitude to do the right thing in places that it otherwise could not.

Some code obliged to do extra work will be able to do less of it when compiled with a compiler that knows C++23, provided you have followed rule of zero.


One tough thing about rule of zero classes is that many to most end up having an implicit 'the object has not been moved from' precondition on methods. The classic example is a class with a unique_ptr member that is never null except after move, so none of the methods have null checks.

I understand why the committee was unable to get destructive moves into the standard, but I do think that is one thing Rust got right that I really wish C++ had.


People bang on about borrow checking etc but really it's move semantics that's the million dollar idea in Rust - it just obliterates a huge Gordian knot of complexity in one stroke


In Rust you cannot write a move constructor. That proves limiting. But Rust move construction, also, cannot fail, which is liberating.

Rust cannot abandon backward compatibility with itself, so will increasingly be stuck with old choices, like C++, and will soon be fully as complex and infuriating. But it will, like C++, remain usually more useful than alternatives.


Agree. Can't really blame C++ for it being difficult to retrofit something like that. It's akin to the lack of const in Java: they'd add it if it were practical to do so.


This is the kind of nuances that make it increasingly hard to look at C++ code and be sure of its semantics, without diving into compiler flags and standardese.


Not to mention the paywalled C++ standards documents.

People say A few hundred dollars is nothing to a professional software engineer, but few C++ developers actually buy it, so the legalese they consult is often not the letter of the law but a proxy like cplusplus.com, replete with the occasional error.


There are the drafts, but even if final standards were available for free, this kind of knowledge is too fine grained to keep all in head across language revisions.

I bet many ISO committee members aren't able to answer "what are the semantics of this code" with looking into the standard.

And there lies the problem, it is not possible as contractor jumping into a foreign code base to be certain of everything that is going on there.

Now imagine the task of upgrading a codebase to a newer compiler and language version, while assuring everything keeps working the same way.


No one is obliged to pay "a few hundred dollars" to see the text of ISO 14882. The Final Draft International Standard online is guaranteed to differ from the formal Standard only on the title page.

But cppreference.com is generally easier to understand.


> "a few hundred dollars"

Care to explain the scare quotes? ISO charge 198CHF, [0] which is $210 USD. ANSI charge $250. [1]

> The Final Draft International Standard online is guaranteed to differ from the formal Standard only on the title page.

Do you have a source for that? From what I can tell the differences are expected to be minor, but that's as far as the guarantee goes.

If what you say is true, why would anyone ever both paying for the completed document, and why would ISO expect to derive profit from paywalling it?

> cppreference.com is generally easier to understand

Sure, I'm not saying it's a worthless website, far from it, but errors can and do creep in.

[0] https://www.iso.org/standard/79358.html

[1] https://webstore.ansi.org/Standards/ISO/ISOIEC148822020


> Care to explain the scare quotes?

Because nobody not contractually obligated needs to pay it. Some entities allow themselves to be so obligated, but you are not obliged to mimic them.

> ...why would ISO expect to derive profit from paywalling it?

ISO is an old organization. For paying it, as with the "Dine and Dance" sign on the hotdog stand, you can if you want to.

> From what I can tell the differences are expected to be minor, but that's as far as the guarantee goes.

That is as far as ISO goes. But SC22/WG21 goes the rest of the way. ISO has no authority to change the text from the FDIS in any way that affects the agreed-upon meaning of the text. In practice this means no changes at all. SC22/WG21 is not obliged to publish its FDIS, but is allowed to. It does; not all WGs do.

Cplusplus.com is, unfortunately, garbage. All we can do about that is say so.

While cppreference.com is not guaranteed to be precisely accurate, you are overwhelmingly less likely to misunderstand it than the Standard, so you come out well ahead. Anywhere it and the Standard differ, your compiler is likely to differ, too.


> nobody not contractually obligated needs to pay it

Oh, right. You could just as well say academia's open access movement makes no sense as no one is pointing a gun at your head making you pay for access to academic publications. That would of course entirely fail to address the motivations behind the open access movement.

> For paying it, as with the "Dine and Dance" sign on the hotdog stand, you can if you want to.

Granted, the standards body gives you the easy option of not accessing the standard. That doesn't strike me as a compelling response. It would be challenging to come up with a model so closed and hostile that this wouldn't apply. (I suppose the Google v. Oracle case will decide whether it's even possible.)

It's perverse that ISO make it their business to obstruct and disincentivise access to standards. Sutter even has the gall to insist that it deserves to be called an open standard, [0] despite that ISO ensure that sharing this open standard is forbidden under copyright law, such that doing so may constitute a criminal offence.

They only paywall the final document because of the perception that there is value in having access to finished document rather than the final draft. You're essentially making the case that everyone who pays for the finished document is a fool, but evidently the perception is that the small differences are worth paying for at least some of the time, or else the paywall on the finished document would not be expected to derive profit. It's the same deal with preprints in academic publishing. But I'm just re-stating my earlier point.

> SC22/WG21 goes the rest of the way. ISO has no authority to change the text from the FDIS in any way that affects the agreed-upon meaning of the text. In practice this means no changes at all. SC22/WG21 is not obliged to publish its FDIS, but is allowed to. It does; not all WGs do.

Thanks, I appreciate the detail.

> Cplusplus.com is, unfortunately, garbage. All we can do about that is say so.

Really that bad? I find it useful as a general reference, but I have encountered subtle errors.

> cppreference.com is not guaranteed to be precisely accurate, you are overwhelmingly less likely to misunderstand it than the Standard, so you come out well ahead

That's an interesting point. 'Language legalese' can be pretty impenetrable, and that's not an issue exclusive to C++. You need to be a fairly advanced student of ANS Forth to make sense of this, [1] for instance. A standards document is not a tutorial, after all.

[0] https://herbsutter.com/2010/03/03/where-can-you-get-the-iso-...

[1] https://forth-standard.org/standard/core/DOES


> as well say academia's open access movement makes no sense as no one is pointing a gun at your head

The difference is that there is in fact no practical reason ever to buy a copy of ISO 14882. It takes contrived legal circumstances, such as a contract in which you are obliged to deliver someone the specific document published by ISO. In practice I have never encountered a real case, so this is a case of straining at gnats.

Recently, ISO relied on sales of Standards to fund its operations. Furthermore, allowing sale of inauthentic copies could endanger the public. But nobody benefits from offering a free download of the wrong thing, so perverse incentives have dissipated.

Anyone who chooses to spend the money can have the ISO document with its authoritative Title Page, and all of the satisfaction that Title Page brings them. They are fools only if they chose that over something else of actual value. Not everybody has a limited budget, or is spending their own money.

Cplusplus.com has a pattern of failing to fix errors it has been notified of, and of failing to keep up with current Standards. Use it if you like being misled. We might like to be able to exercise some authority to take down cplusplus.com, or force them to correct their falsehoods, but there is no such authority. The only real incentive they have to correct errors is to maximize ad revenue, but the marginal value of correctness is evidently limited.

The maintainers of cppreference are users of the Standard, and are inconvenienced by any misperceptions about what the Standard requires, so invest substantial effort to keep it in line, so far as tracking Defect Reports and resolutions.


This is why I never try to do clever things in C++.

Stay on the happy path of not having to be an ISO expert.


Most often the least clever thing is the most clever thing. The best optimization is the one that is not needed at all.

An `std::unique_ptr<>` member is not just safer, it is also faster if it enables the compiler to generate its own various constructors and destructors.


Having a temporary variable to make code more readable is hardly a 'clever thing'


Maybe not in a sane language. But apparently it is in C++.


> Having a temporary variable to make code more readable is hardly a 'clever thing'

Right, but if you're sprinkling your code with temp variables, specially with a language such as C++ where copy elision is far from ensured, then you're already not averse to the idea of instantiating variables left and right.


Copy elision is in the set of things you shouldn’t expect someone who doesn’t program in C++ to know the intricacies of, but is important enough that most people writing a lot of C++ should know it (especially since an extra copy constructor call may have unintended side effects and cause tricky bugs).


Assuming you have proper knowledge of the whole codebase.


The problem is when you try to write boring code, but the compiler or library decides to be clever for you. Now you have a weird bug in otherwise innocuous code that requires an ISO expert to understand.


In my experience that essentially never happens.

One of the complaints about c++ is how many obscure features it has. Most of those obscure features are for authors of the standard library implementations (or other libraries intended for widespread use), not for user code. If you look at the standard library source it’s very hard to read because it’s full of things to transparently handle weird corner cases in order to avoid user surprise.

But in my code I don’t use any of that stuff. There’s a pretty straightforward modern language in the middle of that, and not even a huge one.


I try to write pretty straightforward modern C++ as well, but have been bitten repeatedly by weird corners of the language. For a recent example, I was using the address of some functions from a library, and the library changed to provide the exact same functions in a different way that broke taking their address.

Seems like that particular Abseil ToTW isn’t public, but it alluded to this practice being banned in the C++ STL.

Or for another example, writing code that can be compiled by different versions of a single compiler, clang, e.g. across the Android NDK, XCode, latest LLVM on Linux, and Emscripten, will occasionally give surprising results. To say nothing of supporting msvc or gcc.


Both of your examples hold for C, and for the same reasons. For example trying to take the address of `errno` is UB in C as implementations are free to implement it as a macro (and almost all do).


Good to know, not that I expected C to be any better.


Pardon me, and of course you have no obligation to answer (I'll be googlin' a fury after I post this), but could you perhaps share a resource or two for c++? I ask because this:

> There’s a pretty straightforward modern language in the middle of that, and not even a huge one.

made me literally mutter "woah... really?" I've always had the impression, and granted it is based primarily in ignorance and group think (looks around), that learning C++ is an endeavor that takes years because the language is so complex. And so, I would love any resources that make it more tractable... or does it really boil down to don't use "obscure" features you don't need?


There’s a short book by Stroustrup called “a tour of C++” that shows you the essentials. Get the 2018 edition.

Then read some code and write some code. Don’t worry about efficiency. You can read the “core guidelines” as well which are quite useful, but for a complete beginner many of them will be more than you need.

Like Lisp, C++ is a multi paradigm language: you can write procedural, o-o, and other styles of code. It has a pretty powerful type system.

Don’t worry about old, c-inspired c++. If you know C, as a learner consider c++’s relationship with C to be no different from Java’s or even Python’s relationship with C: some common syntax and conventions. Of course C++ is indeed more tightly coupled with C, but if you think of it that way you will make your life significantly harder, especially when starting out.

I find c++ pretty convenient for rapidly prototyping something that runs reasonably quickly. Then if I care I have enormous power for optimizing...but it’s often unnecessary.


A tour of C++ 2nd edition: https://www.stroustrup.com/tour2.html

C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines

GSL (Core Guidelines Support Library): https://github.com/Microsoft/GSL


I second the recommendation of A Tour of C++. It's not going to give you a detailed explanation of every page of the specification. It's going to show you the basics of using the major parts, and why they are the way they are. From there, start exploring on your own.


Our codebase is a few hundred thousand lines of C++. I think we use enable_if two or three times. Templates are used when necessary. I've been paid to write C++ for about 15 years at this stage, and I still regulary refer to example code online. And if you want to see exactly what the compiler's doing to with your code use Compiler Explorer.


> And if you want to see exactly what the compiler's doing to with your code use Compiler Explorer.

With real code (with a build system, various includes, and likely your employer's proprietary code you aren't allowed to upload to some web site) it's usually far simpler to simply use -S or --save-temps. Sometimes you can make a toy case or take a sample from your employer's code base and paste it into godbolt.

It's very rare for most users to want to see the assembly code.


> It's very rare for most users to want to see the assembly code.

Maybe, but if I'm hiring a C++ programmer I'd expect them to want to do this occasionally.


I learnt a good bit of C++ by going through Elements of Programming Interviews, trawling through cppreference.com and getting help from #C++ on FreeNode for the difficult parts. Coming from mainly functional languages, I actually found it quite fun going through cppreference.com and seeing all the interesting ways in which C++ does things. Especially the STL.

Pick an interesting project and continue in that vein. The biggest difficulty for me was understanding the memory model and all the different types of value, references and so on, but #C++ was again a brilliant resource for smart pointers to good articles on the difficult subjects.

Professional C++ programmers might have other opinions on how best to learn.


The most common confusion among beginner programmers who want to pick C++ as their programming language (in contrast to scripting or interpreted languages) is that you may think that C++ is much harder than "even higher-level" programming languages, which means you rather miss the point of what programming is. Almost every programming language (if we're going to exclude some exotic ones, or domain-specific languages) - i.e. C++, C, Python, Java, etc. - have the same things in common, which are data types and control flows.

When people judge C++ for its complexity - there's a reason for that. C++ is both a syntax and a program. The program is the compiler, and the syntax is the part of the compiler that parses your text file to produce machine code.

What you want to do to learn programming, is first understand why do you want to learn programming and what kind of thing you want to create. Everything you need to know to not write obscure programs in C++ is to learn C. C can provide with every basic concept you need in order to be able to program anything that you want. Maybe, if you invest enough time and dedication, you'll realize that all these fancy features that C++ implements are unnecessary.

Learn operations on strings, dynamic memory management with boundary checking, control flows, simple and abstract data types, etc. But, most importantly, you should have a goal - an idea of what you want to accomplish with that knowledge, otherwise learning the syntax of the language and its "cool new features" will leave you thinking "welp, what do I do now".


> Everything you need to know to not write obscure programs in C++ is to learn C. C can provide with every basic concept you need in order to be able to program anything that you want. Maybe, if you invest enough time and dedication, you'll realize that all these fancy features that C++ implements are unnecessary.

This is very bad advice. C lacks fundamental features like unwind-protect (called RAII in c++ circles) and any metasyntactic constructs at all, all of which (among other things) help you avoid boilerplate (a prime source of bugs).

I write C code, and there is a place for it. I write a lot of Lisp code too, as there is a place for that. But nonsense like the quoted comment is like saying you only need to speak English, or the only vehicle you need is an automobile.


I didn't say that C++ should not be used, and that everyone who uses C++ should switch to C for anything that they would like to program. There's a place for C, and there's a place for Lisp, and there's surely a place for C++.

Culturally, we're at the place where most of people prefer higher-level things that are "easier". The growing complexity of the C++ makes some people who prefer to work on higher-level languages anxious to consider the lower-level languages like C++.

What I was trying to convey is that C is a good start for fundamental concepts of programming and everything you need to start programming in C++ - if you want to specifically use that for your tasks. However, people are different, goals are different, we don't know what are these projects. If you're using C++ to build small programs and you don't care about efficiency - I can only shrug and say "well, you do you".

Again, I did not say "don't use any of C++ cool features". It would be even easier to grasp the complexity of C++ once you understand what's the root of the problem, why people even come up with these features, and why do you want to use them.

In your reply you just jump to conclusions, saying that "no RAII is bad" to someone who probably may never need it.


The part that was most problematic is highlighted below.

> Maybe, if you invest enough time and dedication, you'll realize that all these fancy features that C++ implements are unnecessary.

It's true, you can write anything you could write in C++ in a universal Turing machine. I don't write a lot of assembly any more and haven't written microcode in decades but sure, I could write a web server in assembly code.

unwind-protect is thankfully pretty fundamental to avoid typical resource problems; its more than syntactic sugar. When I started writing C code around 1982 it was the thing I missed the most. Counted strings not only avoid a huge number of security problems and bugs, but have been known to be significantly faster than delimited strings since around the same time.

But sure, if you don't care for them don't use C++.


I'm sorry, I don't understand what you're arguing about. I was replying to a person whose use case for C++ nor me, neither you, understand. It could be simple small programs, not hundreds of thousands of lines of code that needs to be managed by modern programmers who write everything in OOP style.

Different people structure their code differently, and programming is like solving a puzzle. You can have a mindset that makes you choose the modern features. We both have different backgrounds and perspectives. Would you go about using a "garbage collected" language without learning why it has garbage collection in the first place? You would, if your goal is to simply make things work as quick as possible, and just trust the tool that you're using. I do not have that trust when I work on my projects.

When I suggest something that is related to programming, I do it from the perspective of quality (e.g. performance and clarity), and understanding of how things work, and from there you're free to do, choose, pick whatever you want.

>But sure, if you don't care for them don't use C++

We're not helping the person who asked "how to go about learning C++ without being worried about its unnecessary complexity" with these arguments. The point is that you can use C++ without its "Modern" features. And you should, if you care about the quality of your code (clarity and performance).

When I'm talking about "Modern" features, I do not mean things like inferred typing or function overloading or templating. But various std:: function/container implementations.


Maybe I am missing something, but when we care about unwind-protect, I would rather stay with safer languages than C++.


Unwind-protect is not just for memory; consider with-open-file.


Both Lisp examples, and mechanisms available in most modern languages with automatic memory management, no need to dive into unsafe C++ code for them.


“Everything you need to know to not write obscure programs in C++ is to learn C. C can provide with every basic concept you need in order to be able to program anything that you want. […]

Learn operations on strings, dynamic memory management with boundary checking, control flows, simple and abstract data types, etc.“

_If_ you start with C, I would skip the “operations on strings” part. C doesn’t have strings. It has a set of poorly designed functions that operate on sequences of contiguously instances of char* that satisfy the constraint that they contain exactly one char* whose value is zero: the last char in the sequence.

Nowadays, I wouldn’t start with C, though. I think it’s more distant from modern C++ than python, go, or scala (yes, you can write C++ in C style, but you shouldn’t)


I don't know if I am replying to an expert programmer, who writes programs on the daily basis, but you contradict yourself:

>C doesn’t have strings. It has a set of poorly designed functions that operate on sequences of contiguously instances of char*

Which reads as "C doesn't have strings, it has strings". You may have confused "std::string", which is a container, with the concept of strings as "array of characters". If so, then I'm clarifying, that I've meant the latter.

If you wanted to emphasize that "char" is worse than "std::string", then I do not agree with you. The reason is that, well, as I already said - "std::string" is a container, which means using it causes unnecessary inefficiency. How do you think it was implemented? C++ was written in C, by using its functions and data types.

Your phrasing of what "char" is reads like it is a some sort of a problem, however it's the way that things work on low level and by saying

>I would skip the “operations on strings” part

you would mean that you wouldn't want the person, whom I was replying to, to know about what you've wrote about "char*".

>Nowadays, I wouldn’t start with C

We don't know who you are, and what kind of programming you do, and how much you care about quality programming. The person I was replying to seemed to be interested in lower-level programming languages, so I assumed he's a some sort of a programmer who wants to understand if it's possible to write in C++ without the "Modern" stuff (which is possible, and this is what expert programmers that write quality software do).


> write in C++ without the "Modern" stuff (which is possible, and this is what expert programmers that write quality software do).

Idiomatic C code is an unoptimizable mess consisting of layers of `void*` indirections, violations of strict aliasing, project-specific linked list structs and poor reimplementations of other basic data structures, `strdup()` and fixed arrays everywhere... 99% of new C projects written after 2010 and outside of the field of embedded development are made by fetishists and are neither quality software engineering nor performant, but the people who still do this stuff when they're already in a .cpp file are even worse.


> Idiomatic C code is...

Sorry, that reply of mine was rude. I get where you're coming from. C is a small language that can be quickly learned by a beginner, and familiarity with much of it is still unavoidable in C++. There's nothing wrong with enjoying the C language. I must assume that you have good reasons for avoiding modern features in your particular projects. I got triggered by that statement because of some code I've been working with recently.


Haha, yeah. I've read your first comment and thought to not bother with replying, as it's expected that majority of people out there are polarized by the media that tells them that "X is bad, all hail Y" (especially, modern web tech...), and I don't want to be a part of pointless discussions.

You're yet another person who replied to my comments who argued against something different, i.e. you assumed that what I stated in my parent comment is that "you should program in C and enjoy doing it".

In reality, I'm sharing my experience by telling that there's nothing scary about C++ once you know how to use C [so you can use the "Modern" part of C++ only when you will be in need to do that]. Many people do mistakes, but that's because they did not contribute enough time to understand the language and the principles of programming in general. Therefore, many of these people are eager to discuss about things that are not that important; imagine if they were to discuss something like how to make things better, instead of praising solutions that cause more friction (e.g. there's nothing wrong with having a programming language that gives you features like C++, but the important part is how they are implemented, because it depends on the efficiency of your program).

People pretend to be experts, and miscommunication is one of the problems, which is also seen in this community, where people would argue even when all you need to do is ask a question if you didn't understand something, instead of insulting or saying that the advice is bad.

>C is a small language that can be quickly learned by a beginner

The C syntax can be learned, but it's not what's important. The important part is to understand how to use it, the C itself, and the concepts of programming, which is what beginners usually struggle with.

>avoiding modern features in your particular projects

The reason is the efficiency: how performant the program is (rendering 60 frames per second is a huge data stream, among other calculations); and the clarity (I like when my code is simple, and is not a mess).


> "std::string" is a container, which means using it causes unnecessary inefficiency.

std::string is a particular data structure, consisting of a pointer to a heap allocation, a length value, and a capacity value; or alternately a "small string" that fits into the std::string structure itself.

There is nothing inherently inefficient about using std::string compared to implementing a similar data structure by hand; in that sense it's a "zero-cost abstraction". You can benefit from doing it yourself if you want a different data structure that provides a different feature set, such as if you don't need dynamic resizing, or if you want to refer to existing memory directly rather than giving each string its own heap allocation. But the data structure implemented by std::string is a reasonable choice for lots of scenarios. (Alternately, you might be able to just write a more efficient implementation of std::string than the implementation in whatever standard library you're using, but most standard libraries are reasonably well optimized.)

> How do you think it was implemented? C++ was written in C, by using its functions and data types.

All C++ standard library implementations, and most C++ compilers, are written in C++. The standard library does of course use primitive types such as pointers, which are shared with C, but that's not the same thing as being written in C. That said, most C++ standard library implementations do use some C standard library functions, though they don't have to. In particular, std::string implementations are likely to use memcpy/memmove to copy strings, and malloc for the heap allocations.

But they don't use the 'str' family of C standard library functions, the functions one would typically learn about when learning how to use strings in C. They can't, since those functions don't handle interior nul bytes, which std::string is required to support. Those functions are also unnecessarily inefficient when you know the string length, as std::string does.

> write in C++ without the "Modern" stuff (which is possible, and this is what expert programmers that write quality software do).

Expert C++ programmers know which parts of "modern" C++ have zero cost and can be used freely, which parts have low but acceptable cost compared to the increased memory safety and ergonomics, and which parts are best to avoid. Programmers who avoid the modern stuff entirely are mostly those who don't know C++ very well.

That said, C++ is a convoluted language that is unnecessarily difficult to become an expert in, and unnecessarily difficult (but not impossible) to write efficient code in even if you are an expert.


I'm sorry, I don't understand why am being educated here, meanwhile I know how things work, both in C and C++. I literally got downvoted for contributing my opinion (not an advice) on what a person whom I replied wanted to know to better understand C++ without its complexity, because some people thought "that you shouldn't learn how to work with strings". Which is ridiculous. Now I regret doing so.

But I've read what you wrote anyway. You contradict yourself here:

>There is nothing inherently inefficient about using std::string

>it's a "zero-cost abstraction"

>You can benefit from doing it yourself

You could've asked if I don't know something that I'm talking about, I could've clarified.

> But the data structure implemented by std::string is a reasonable choice for lots of scenarios

For example, these "lots of scenarios" do not include the projects I'm working on, and for a good reason.

>most C++ compilers, are written in C++

I'm aware of self-hosting. The point that I was trying to convey is that, at some point, the "unsafe and poor C" was used in order to create that language, and further improve it.

>Expert C++ programmers know which parts of "modern" C++ have zero cost and can be used freely

And those are things that do not start with "std::"

>unnecessarily difficult to become an expert in

There's a difference between being an expert software engineer and expert in a particular programming language's architecture. First one involves understanding systems and know how to solve problems efficiently; second one doesn't require you writing complex software, rather learn from the docs and stay up to date with the new features without ever programming quality software.


> I'm aware of self-hosting. The point that I was trying to convey is that, at some point, the "unsafe and poor C" was used in order to create that language, and further improve it.

You wrote:

> "std::string" is a container, which means using it causes unnecessary inefficiency. How do you think it was implemented? C++ was written in C, by using its functions and data types.

Your wording strongly implies that the implementation of std::string is written in C, or at least that C++ is somehow layered over C in a way that explains the "unnecessary inefficiency" (which you could bypass by using C directly).

The fact that the first C++ compilers, historically speaking, were written in C seems quite irrelevant.

Perhaps you were trying to convey that the implementation of the C++ standard library is more C-like than typical C++, because it is lower-level and can't rely on the bits of the standard library that it's trying to implement? But that is also not what you said.

> > Expert C++ programmers know which parts of "modern" C++ have zero cost and can be used freely

> And those are things that do not start with "std::"

If you are as much of an expert as you say, I assume you are engaging in rhetorical hyperbole.


This discussion is really off-topic, but whatever, it seems like something can be possibly learned from it.

>Your wording strongly implies ... that C++ is somehow layered over C

Yes, I did not mean that, I should've written the next sentence on the new line to not create that confusion.

>The fact that the first C++ compilers, historically speaking, were written in C seems quite irrelevant.

Let me rephrase what I've meant with the std::string example. If you want to understand my perspective, you should try to think about things as they are. What is std::string really? What does it do? What happens when you use it? When do you use it? What's the purpose of it in the place where you need to use it?

Now, hear me out: in majority of cases (I've read lots of codebases, majority of them are eye-bleeding), people think it's just a way to declare a string. Technically, it is. However, "string" is not a type, really. It's a container like a "vector". That means, that it has a bunch of things inside, that you can also use: could be functions, could be some other things.

When you care about efficiency (and the complexity of your program), you care about what you use, and what's the final output that you get after you feed your code to the compiler. Now, majority of people assume (or don't and they just don't know about that) that C++ probably has everything right and everything is good, because there are super smart people working on it, and you should trust the compiler no matter what; and they will defend any decision that C++ authors will make.

There's nothing really bad at implementing features similar to those of the "Modern C++". What matters is how they are implemented. C++, generally speaking, is inefficient by design. So, it's not that the programming language itself is horrible, but rather compilers. It's possible that someone would write a highly efficient C++ compiler that compiles 1M LOC/s and it has a "string" type, and if you want to use some additional features to do operations on them, you wouldn't need to include thousands of unnecessary lines of code to your generated program. But, today, this is not true with any C++ compiler (GNU C++, MSVC, ICC, etc.).

What people seem to miss, is that it's possible to use better implementations (that are available for free!) of many C++'s standard library functions, especially for data structures and algorithms. Now, if you take the "minimalism" of C, get rid of "classes" (there are structs), and use better implementations - wouldn't that be better than what C++ STD has to suggest? Practice shows, it is. And experts know that (that's why they are experts, not because they have X years of experience at a brand-level company, but because they know what they do and it was proven in practice).

How to understand my perspective? You should think about memory. And the code that your compiler produces. Now, apply the memory layout to your generated program and see what's going on. In most of the cases, where people happily use C++ with std::, what happens is that each call goes through a maze of branching. And what I'm trying to say is that that branching is unnecessary. Why would I use something like that, when I can not use it and perfectly do my job without any pain that people talk about all the time?

All in all, the reasons to not use these things are performance. You may not be in an agreement with me, because you may be one of the folks who are from the "solving problems without caring about efficiency" camp.

And let me actually provide you with an example:

Let's assume we're a beginner modern C++ programmer, who's enthusiastic about programming (so it's most likely that I will use Visual Studio with MSVC!). And we also want to use std::string. So, let's do that!

  #include <iostream>
  
  int main()
  {
      std::string str = "hello";
  
      std::cout << str << std::endl;
  }
The code above looks pretty much simple, we just ask the compiler to output "hello". Nothing seems suspicious about that. Now, let's step into our code and see where it goes for just std::string (ignoring ::cout and ::endl):

  basic_string(...)
  constexpr explicit _Compressed_pair(...)
  constexpr allocator() noexcept {}
  constexpr explicit _Compressed_pair(...)
  _String_val() noexcept : _Bx(), _Mysize(0), _Myres(0) {}
  _Container_base12() noexcept : _Myproxy(nullptr) {}
  _String_val() noexcept : _Bx(), _Mysize(0), _Myres(0) {}
  _Bxty() noexcept {}
  _String_val() noexcept : _Bx(), _Mysize(0), _Myres(0) {}
  constexpr explicit _Compressed_pair(...)
  basic_string(...)
  _Alty& _Getal()
  constexpr _Ty1& _Get_first() noexcept
  constexpr allocator(const allocator<_Other>&) noexcept {}
  _Container_proxy_ptr12(_Alloc& _Al_, _Container_base12& _Mycont)
  _NODISCARD __declspec(allocator) _Ty\* allocate(_CRT_GUARDOVERFLOW const size_t _Count)
  _NODISCARD constexpr size_t _Get_size_of_n(const size_t _Count)
  __declspec(allocator) void\* _Allocate(const size_t _Bytes)
  __declspec(allocator) static void\* _Allocate(const size_t _Bytes)
  _NODISCARD constexpr _Ty\* _Unfancy(_Ty\* _Ptr) noexcept
  _NODISCARD constexpr _Ty\* addressof(_Ty& _Val) noexcept
  void _Construct_in_place(...
  _NODISCARD constexpr _Ty\* addressof(_Ty& _Val) 
  _NODISCARD void\* _Voidify_iter(_Iter _It) noexcept
  void _Tidy_init() noexcept
  static _CONSTEXPR17 void assign(_Elem& _Left, const _Elem& _Right) noexcept
  basic_string& assign(_In_z_ const _Elem\* const _Ptr)
  _NODISCARD static _CONSTEXPR17 size_t length(_In_z_ const _Elem\* const _First)
  basic_string& assign(_In_reads_(_Count) const _Elem\* const _Ptr, _CRT_GUARDOVERFLOW const size_type _Count)
  value_type\* _Myptr() noexcept
  bool _Large_string_engaged() const noexcept
  _CSTD memmove(_First1, _First2, _Count \* sizeof(_Elem));
  static _CONSTEXPR17 void assign(_Elem& _Left, const _Elem& _Right) noexcept
  void _Release() noexcept
  ~_Container_proxy_ptr12()
And those are all functions.

What if we were to use "const char*" instead, if our intentions were to just output a simple string? Nothing. It would not travel to any library, because it's not a library implementation. And if I were to make my own string handler, it wouldn't be as messy. Ah, the resulting code with "std::string" is 60KB (that's bonkers!), meanwhile with "const char*" it's 40KB (still bonkers!).

> the implementation of the C++ standard library is more C-like than typical C++

Yes, I did not say that, and I did not think about that.

>as much of an expert as you say

I never said that I'm an expert, though!


Side point:

> I literally got downvoted for contributing my opinion

I noticed that but my one upvote was not sufficient.

Discussing upvoting / downvoting is discouraged on this site but I consider that unfortunate, as the votes (well, downvotes in particular) are part of the signal.

Unfortunately they are also frequently used for "I disagree" rather than "this is off topic". I disagree with you but like every other user have the opportunity to express that by typing a response.


My problem is mostly the libraries, and in-house code written by other contractors, not all of them have been written at the same quality level as the C++ standard library.

Contracting is the main point that makes me happy not having to deal daily with C++.


Finding bugs in the standard library is doubly "fun".


Channelling your inner Sartre or weary voice of experience?


Experience of handling code written by all known consumers of H1B visas in US and other major sweat shops, across industries whose main business isn't selling software.

The real world isn't the pleasant CPPConf and C++Now session rooms, nor the quality standards that FAANG expect on their work processes.

The mess that is done will less complex languages takes another level when they also have touched C++ in the process.

Remember some Bjarne talks, on the real world many students still get introduced to C++ via Turbo C++ for MS-DOS.


Indeed, I won't believe anyone that asserts they can fit on their head all UB usecases that are documented on the standard (> 200).

Nowadays I just use it for native library wrappers, as it is better than plain C, but still hoping for FFI improvements on the languages I use that will reduce that need even further.


C++ is a value semantic language. Copies are implicit and references are explicit (expressed in the syntax). Many other languages are reference oriented, and references are implicit and copies are explicit. Both have pros and cons, and in both you sometimes want to cross the aisle.

When you consider that references in C++ need to be explicit you have the following solution:

https://gcc.godbolt.org/z/PGbc46cxK


When I learned about copy elision, I learned about it in the context of learning about RVO, which is a special case in C++. Otherwise I had no reason to generally assume the compiler would elide copies.

I don’t understand what caused the author to assume that a compiler would elide the copy when passing a value argument to a function. That seems like an unfounded assumption to me and I don’t think there is anything sad about it not being true.

To avoid the copy in all cases the “consume” function should probably accept a reference argument. That would be idiomatic C++.


This. The compiler is doing the least surprising thing here - I don't understand the author's confusion.


By accepting a reference argument, you introduce pointer deref. Wouldn't it be better to use

    Widget && temp = complicated_long_function(...);
    consume(temp);
Am i missing something?


That won’t work. Copy elisions only happen when they are copy constructed from prvalues, not simply an rvalue.

The pointer deref is unavoidable if you pass a large enough object by value, which is what’s happening here. On x86_64, generally values larger than 16 bytes are passed by reference transparently.


I see, thanks!


Your proposed example code does exactly what GP said, pass as a reference (in your case, an rvalue reference).


Not quite, I suggested changing the signature of “consume” to accept a reference. His example doesn’t do anything to invoke copy elision, since his named rvalue reference will be function cast to a normal reference and then copy-constructed into the stack value passed to “consume”.


https://gcc.godbolt.org/z/qd5qnhPT6

How does any copy-construction happen here?


Not optimizing when creating a temporary variable is unfortunate. I sometimes create those for debugging purposes, give them a descriptive name, then leave it in for documentation purposes after the debugging is complete. Typically for me, this isn't the sort of code that executes in a tight loop, so is unlikely to impact performance.

Edit: I agree with the other commenter about taking in a Widget by reference instead of by value in the consume function.


If the routine is truly going to consume the widget, then you’re paying for a copy within the routine if you take the parameter by reference. Unfortunately that’s just kicking the can down the road.

I’d prefer code like this use the move operation to make the code reasonably efficient and maintainable. Then, only after its proven the line is on a critical performance path, it can be further optimized later.


This blog post is great, but I think it ends too quickly. It goes into a lot of detail, but then skims over this "few circumstances where copy elision will happen" without the same level of detail or examples. And it doesn't provide examples of copy constructors, or explain anything about what happens if you delete the copy constructor.

This blog post was just getting good, then it stopped!


So, don't do non-trivial things in copy constructors. In fact, try your best to avoid writing copy constructors at all - I can't remember the last time I wrote one.

Unless you are an experienced library writer you should think very, very hard about writing copy or move constructors (and even then, think again).


Copy constructors are usually necessary when wrapping data on the heap or an OS handle. I agree this is relatively rare in user code but what’s not necessarily rare is having one of those types as members in an otherwise trivial type. std::string is a good example.


> std::string is a good example

Yeah, so USE std::string in your classes, then no copy constructors written by you will be needed. If you find yourself heap-allocating things explicitly with "new", then you are almost certainly doing something wrong.


This seems mostly irrelevant to the example in the post.

Why does it matter who wrote the copy constructor? In the author's example, they did not write a copy constructor, they USEd std::string.


Yes but this article is not about the hazards of using new/delete. It’s about the hazards of custom copy constructors. Simply using a common type like std::string is sufficient to trigger that hazard even if you never write a custom copy constructor yourself.


Perhaps you could point out the hazards? The generated code is longer, but so what, so long as it is semantically correct - longer code is not a hazard. And without knowing how the code was built, with which compiler, with which options etc. it's very hard to comment.


Bigger ASM means you can fit less code in cache, which makes your code slower. If this happens in a hot loop, it may be the difference between "my code fits entirely in L1 cache" and "my code needs to be fetched from L2/L3".

This matters in high-performance scenarios. The fact that you can introduce a performance issue simply by creating a temporary is absolutely bonkers, and makes writing high-performance code more complicated than it needs to be (and god knows it's already very complicated).


Depending on the processor, it can also be the difference between instructions in a tight loop only needing to be decoded once (because of micro-op caching) vs. decoded each loop iteration.


> without knowing how the code was built

The article links out to Compiler Explorer (https://godbolt.org/), which shows the exact compiler version and options and lets you play with different ones. It shouldn’t matter which compiler — the standard prohibits optimizing this.


C++ will make implicit copy constructors for you, for simple classes. But those can he deleted, if you really want to be sure they won't be used


Copy elision should only be thought of in terms of prvalues. As soon as you give something an l-value, all bets are off. And I don’t really like the “consume” example since if you want to avoid copies into functions like that you should really be defining the function as taking Widget& and not rely on what you think is copy elision.


"The Sad Truth" is that you have to be a little bit cautious/knowledgable in order to take advantage of a potential optmization?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: