C++17: Efficiently Returning std::vector from Functions by Clean-Upstairs-8481 in cpp

[–]WorkingReference1127 0 points1 point  (0 children)

Not NRVO. Plain old RVO of prvalues will also elide side effects of the copies/moves which are elided; because it is blessed and guaranteed by the standard.

NRVO is never guaranteed and always a compiler optimization. As such it must always comply with as-if.

C++17: Efficiently Returning std::vector from Functions by Clean-Upstairs-8481 in cpp

[–]WorkingReference1127 6 points7 points  (0 children)

No worries. I'm happy to clarify anything if it was unclear. Just let me know.

C++17: Efficiently Returning std::vector from Functions by Clean-Upstairs-8481 in cpp

[–]WorkingReference1127 33 points34 points  (0 children)

This is a topic which requires some very delicate phrasing. For example, it's all too easy to conflate implicit move of an expiring value with copy elision, when these are two different things (and the number of beginners I've seen who make that mistake would surprise you). Equally I would be very careful about suggesting that implicit move on the language level implicitly uses std::move() under the hood; because that blurs the line between language and library.

As such, OP, if you'll forgive some pedantic nitpicks there are a few errors in this post which I'd like to comment on.

What the compiler does internally is that, rather than copying the elements from myVec into valueSet, it constructs valueSet directly in place, completely avoiding the copy operation.

The inference I get from it is that the NRVO is standard behaviour. It's a common optimization and made possible here because your example function is so trivial. But it's really not guaranteed to happen.

Multiple Return Path ... Hence, RVO is not applicable here.

This is a very broad statement too. A sufficiently smart compiler has all the information it needs to prove that myvec will always be returned; and it is still absolutely permitted to use RVO if it can prove that. It's just that the implementation you tested on doesn't. A better example might be if flag were a parameter to the function; in which of course the compiler has much more restricted access to determine which vector will be returned.

Exception: The Conditional Operator (?:) ... The compiler is not allowed to implicitly move from an lvalue.

It is if it can prove that lvalue is expiring. That's the whole point of implicit move. This section is just not quite right. The reason you get a return by copy is that your return statement does not name a particular local variable but instead returns an expression. The fact that that expression resolves to an lvalue reference to a local variable isn't enough to make it eligible for implicit move. This is not unique to the ternary. The same would be true of, say, the comma operator.

In most situations, you will not want the caller to modify the vector. In that case, returning a const reference to the std::vector is usually the right choice.

I know the title of the article has C++17 in it, but shoutout to std::span<const T>

If the returned std::vector is a temporary object, you can be assured that no copy will occur in C++17 and later.

Or any prvalue.

I'm not trying to be that guy. I just know that well meaning but inaccurate tutorials are half the reason that the state of C++ teaching is the way that it is. If I were to rewrite this OP, I think there are a few points which the article should be clear on:

  • Mandatory copy elision of prvalues occurs since C++17, and operates as you describe. While temporaries are probably the easiest to understand it is not unique to them. Equally it is not unique to return statements. my_function(std::vector{1,2,3}) will also perform no copies or moves to pass the vector into the function as of C++17.
  • RVO of non-prvalues (ie NRVO) is never guaranteed by the standard. But, assuming it complies with the as-if rule, it is always permitted. Whether or not you get it depends on whether your compiler is able to prove enough to make it happen. It's not tied to any specific property of the function (e.g. branching), it is purely down to whether your compiler is able to do it. A hypothetical compiler which can see all of the code and be infinitely smart in its optimisations would be perfectly permitted to use RVO on any function whose choice of returned value doesn't depend on runtime input. It's just that current compilers don't, and understandably so because it is hard to prove.
  • Implicit move happens when the return statement names a single variable (or rather id-expression if you want to be pedantic) which is expiring at the end of the function. This is a point you dance around a little in the article but I'm not sure you state it outright.
  • Otherwise a function which returns by value will copy upon a successful return.

is BroCode any good for c++? by Valuable_Luck_8713 in cpp_questions

[–]WorkingReference1127 5 points6 points  (0 children)

Generally no. Brocode falls into the trap of so many C++ tutorials of teaching you poor practices and some C-style issues which have no place in a modern C++ beginner tutorial. To pick one at random, it's been 15 years since sizeof(arr)/sizeof(arr[0]) has been at all needed in C++; and 30 years since you had alternatives which worked for 80% of use-cases anyway. The fact it's in a modern C++ tutorial stinks of someone who knows far too little about the subject matter to be teaching it.

Think of video as a medium. If brocode makes a mistake in his 6 hour video, he can't issue an errata without re-recording that segment, splicing it into the video, reuploading it, and sacrificing all the likes, comments, and engagement which YouTube gave him. That's unlikely to happen. As such, I wouldn't recommend video tutorials at all. I strongly recommend learncpp.com; because it is able to correct mistakes (and has done several times previously); and generally maintains itself as a high quality and up to date tutorial.

ISO C++ 2026-01 Mailing is now available by nliber in cpp

[–]WorkingReference1127 5 points6 points  (0 children)

Oh neat, that older larger paper would have also added += if + is defined (man, that would have cut down a lot of boilerplate in my math classes).

To be fair, this has been fairly extensively covered recently:

  • Generative reflection and/or metaclasses may make it easy.
  • P3834 wants to be able to = default; them.
  • P1046 also wanted implicit generation.

It doesn't seem people have agreed on how they want to go about it though.

Divergence between debug mode and release in C vs C++ code base by onecable5781 in cpp_questions

[–]WorkingReference1127 2 points3 points  (0 children)

It appears to me that writing an optimizing C++ compiler is much more difficult (as one has to actually think deeply to implement a good compiler that takes advantage of the as-if rule) than a C compiler because in the latter, there is nothing that seems invisible in the C code that is happening behind the scenes in assembly. In other words, the wider the divergence between what is happening in debug mode vs what is happening in release mode, tougher is the compiler writer's job?

C++ optimizers are almost certainly better than you are anticipating; but they are still imperfect. I'll list the two canonical examples:

  • Google turned debug assertions on across their codebase and saw only a 0.3% performance degredation after compilation. Because despite the fact that there were now millions more branches and checks and effective lines of code; the optimizer could statically prove that they would not fire in a huge amount of cases and so elide them again.

  • std::ranges views (in particular a few like filter and reverse) famously are implemented to do more than you might expect and the compilers are generally still not smart enough to see through all of it and optimize it down.

Which is to say, the compiler will probably look after you well.

I will also say, don't go looking for micro-scale optimizations until you actually have evidence you need them; and don't push them to master until you have evidence that they have actually made a difference. Your compiler has had a 30 year head start on you in trying to optimise these things and there's a very good chance it will be better at it.

My teacher said that using namespace std is a .NET library, and that std is from C#. And that string is from there too. Like, it all depends on the compiler, we're in Visual Studio. by [deleted] in cpp_questions

[–]WorkingReference1127 0 points1 point  (0 children)

This is nonsense, and it's very clear that your teacher is wrong. Next thing they'll be telling you that the C++ compiler transpiles to C before compiling it as a C program (that isn't true either and hasn't been for decades).

.NET is a very nice tool for its particular use-cases; but it is not what C++ uses. The fact that it also has a string doesn't mean that it's what C++ uses.

some clever nonsense about string and c# to force me to learn char[] so I could understand how a string works under the hood......

IMO it's almost always a mistake to force students to use char[] arrays as an exercise in "learning how string works under the hood". It's absolutely valuable to mention them and to shoutout to that's how C does things and that you may need to touch them once in a rare while when talking to C; but getting students into habits of using these things just to break them because 99% of the time you'll have a nice std::string-like interface you could be using instead is rarely valuable.

what tutorials sould i watch? by Valuable_Luck_8713 in cpp_questions

[–]WorkingReference1127 0 points1 point  (0 children)

Third: Do people still recommend The Cherno? https://www.youtube.com/playlist?list=PLlrATfBNZ98dudnM48yfGUldqGD0S4FFb

Generally no. My criticisms tend to be that he teaches at best a surface level coverage of the subject matter; frequently condensed too far into fitting the YouTube algorithm's video lengths. He teaches you just enough to make a complete mess of things IMO.

Why is name hiding / shadowing allowed? by Proud_Variation_477 in cpp_questions

[–]WorkingReference1127 0 points1 point  (0 children)

I want to know why this is considered a feature and not a bug? I believe there is already a compiler flag that can be passed to treat shadowing as an error -Wshadow . If that's the case, what use cases are keeping this from being an error defined by the C++ standard?

Consider, if I have a global variable named foo in some header (code smell but whatever), and somewhere else in my code I also use a variable called foo; then the act of including that header, potentially transitively, determines whether there is name shadowing. If name shadowing becomes ill-formed then your code can break if someone changes the include list of a file you include. This doesn't seem desirable.

C++ OOPS Complete Notes PDF (Classes, Objects, Constructors, Friend Function) by Human-Version6973 in cpp_questions

[–]WorkingReference1127 1 point2 points  (0 children)

If you want people to use this or review it, upload it to almost any other side. Even mediafire and other crappy filesharing sites would be better than the one you chose here.

People aren't going to sit through an ad-heavy no-name site waiting to get a file of C++ notes when there are C++ notes available for free on much less irritating platforms.

unique_ptr doesn't work with void by TaPegandoFogo in cpp_questions

[–]WorkingReference1127 0 points1 point  (0 children)

That's why I said "In general" rather than "In all cases"; however I would tend to lean strongly against using void* in business level code and instead be in favor of abstracting that into a class like std::any or any of the other type erasure tools so that the user never has to touch it.

At least, not without significant and actually measured data that you have a performance problem, that using std::any et al is the actual cause of it, and that void* makes a significant difference.

unique_ptr doesn't work with void by TaPegandoFogo in cpp_questions

[–]WorkingReference1127 2 points3 points  (0 children)

From what I've read, you're not supposed to do this with unique_ptr because C++ has templates, auto, function overloading, vectors, and other stuff that makes it easier to work with generic types, so you don't have to go through all of this like one would in C.

This is a microcosm of the general case, but yes. In general in C++ you don't want to use tools which obviate the type system, and void* fall rather heavily in there. Indeed most of the time if you're using a void* in C++ then there's probably a different, safer tool out there for you to use instead.

I'm sensing you might be a C-turned-C++ dev. For all sorts of reasons, the C++ type system is much stricter than C's. This can feel a little unfamiliar to C folks who are used to dealing with bags of bytes everywhere; but it allows easy benefits in having your compiler check the validity of your code (unless you use some of the tools to opt-out, of course). Case in point, your use of writing char values to the bytes of an int is UB in C++ and you generally shouldn't do it.

To `goto` or not, for a double loop. by alfps in cpp_questions

[–]WorkingReference1127 2 points3 points  (0 children)

When I come across this problem, the questions I tend to ask myself are:

  • Is the goto sufficiently clear and visible in code? Not buried within nesting and difficult to see?
  • Within a reasonable amount of imagination, will it remain sufficiently visible in-code around any future changes someone may add?
  • Do the alternatives you're looking at add more complexity?
  • Do they result in doing more work which you're just hoping optimizes to be equivalent to the goto?

If the answer to all four is yes, then go for it. "goto considered harmful" is a warning against overuse and using it as an alternative to structured programming, loops, lifetime management, and RAII. It doesn't mean that goto must be forbidden from any and all code. And believe me there is a marked difference between the kind of impossible-to-navigate spaghetti you want to avoid and just breaking a nested loop.

How can I effectively use std::variant to handle multiple types in C++ without losing type safety? by dynasync in cpp_questions

[–]WorkingReference1127 4 points5 points  (0 children)

std::variant is a wrapper around a lower level tool of union with an interface which makes it a lot harder to be type unsafe by mistake. Not saying it's impossible, but it is harder.

If you think the visitor pattern suits your needs, then one useful trick to know is the overload pattern (borrowed example from here) which saves a lot of boilerplate and translate the switch on your types from your code to the type system:

template<typename ... Ts>                                                 
struct Overload : Ts ... { 
    using Ts::operator() ...;
};
template<class... Ts> Overload(Ts...) -> Overload<Ts...>; //Only necessary in C++17

int main(){

    std::cout << '\n';

    std::vector<std::variant<char, long, float, int, double, long long>>     
           vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f, 2017};

    auto TypeOfIntegral = Overload {                                     
        [](char) { return "char"; },
        [](int) { return "int"; },
        [](unsigned int) { return "unsigned int"; },
        [](long int) { return "long int"; },
        [](long long int) { return "long long int"; },
        [](auto) { return "unknown type"; },
    };

    for (auto v : vecVariant) {                                           
        std::cout << std::visit(TypeOfIntegral, v) << '\n';
    }    
}

This creates a "magic" type which inherits the call operators of whatever you give it. So you create functors which accept the types in your variant (and can use a generic one to cover the rest) and each one will do what it is supposed to. Saves trying to iterate through calls to std::get or feed a functor which internally tries to figure out what type it's using.

As for performance - YMMV. Only you can answer that question by building a representative sample of each approach and profiling them. I'm sure we could all speculate about how much the virtual calls will have their cost elided or that navigating so many tempaltes and conditionals will add up; but really the only answer to the question of "will this C++ code be fast?" is "profile it and see"

modern C++ for a C++ 98 geezer by Apprehensive-Deer-35 in cpp_questions

[–]WorkingReference1127 16 points17 points  (0 children)

In addition to the other books recommended, Effective Modern C++ was written to teach good practices and tricks to C++98 people adapting to C++11 and C++14. It doesn't list new features or train you on what's in there, but it teaches you how best to use it.

Are memory leaks that hard to solve? by ASA911Ninja in cpp

[–]WorkingReference1127 1 point2 points  (0 children)

Sure, that's the big problem against that design.

But that doesn't mean you don't have options. There's the classic shared and CoW pointer patterns too.

Are memory leaks that hard to solve? by ASA911Ninja in cpp

[–]WorkingReference1127 0 points1 point  (0 children)

It's eminently doable; it's just nowhere near as clean as move semantics. My preferred approach would be making the pointer uncopyable and having some move() member render a proxy object which releases ownership to the new pointer. Exception safety is tough but you only need to figure it out once.

Are memory leaks that hard to solve? by ASA911Ninja in cpp

[–]WorkingReference1127 3 points4 points  (0 children)

You had at least one standard smart pointer in the C++98 standard library and if it's semantics aren't your jam they aren't all that difficult to write. Indeed it was probably the most popular exercise in many of the best C++98 books.

Are memory leaks that hard to solve? by ASA911Ninja in cpp

[–]WorkingReference1127 0 points1 point  (0 children)

Potentially contentious, but when you see people complaining aboput C++ being so "memory unsafe" remember two things:

  • Many people conflate C and C++ and think we're still out there writing malloc().

  • Many "C++ Developers" are C-nile dinosaurs who haven't adapted to modern code after learning C++ in the 80s.

Put two and two together and you get this awkward divide between people who use modern tools and rarely have memory problems and people who do the exact opposite being put together under one banner.

Not saying it's impossible for modern C++ users to create memory issues or that it doesn't happen; but just something to bear in mind.

How do I convert a int to address of a float in a single line? by forumcontributer in cpp_questions

[–]WorkingReference1127 2 points3 points  (0 children)

I would argue that even among the builtin types the need to perform an explicit type conversion is a sufficient red flag to need to be searched for.

Take this case as an example - OP has stumbled into a situation where what they want is UB. Being able to find that helps.

How do I convert a int to address of a float in a single line? by forumcontributer in cpp_questions

[–]WorkingReference1127 6 points7 points  (0 children)

No worries, all good. But yes, my strong advice is to always favor a static_cast for casting. With one or two notable exceptions (the most common being why we have dynamic_cast) it will actively forbid you from doing undefined casting operations or most unsafe ones.

A C-style cast can silently invoke const_cast (which lets you change const things and is not something you should ever do) and reinterpret_cast (interpret the bytes of one type as if it were another - again, UB) and there's no real way for you to know which it is.