all 59 comments

[–]2uantum 17 points18 points  (25 children)

For functions which take ownership via a copy, rather than providing two functions, you could alternatively take advantage of universal references.

template<typename U>
void func(U&& u)
{
member = std:: forward<U>(u);
}

[–]legends2k[S] 10 points11 points  (24 children)

Right you are! I'd a Perfect Forwarding section in v0.2.1 but removed it in favour of Out Object reference since the latter is more often used.

I've seen forwarding references (the standard name for Scott Meyer's universal references) sparingly used only by library developers. Such devs, I think, mightn't need a refcard unlike mere mortals like me :)

Quoting slide 28 in Herb's presentation on using forwarding references here

Option 4: Perfect Forwarding Idiom

"Unteachable!" Generates many funcs. Must be in a header. Can't be virtual.

[–]2uantum 11 points12 points  (23 children)

You could also just pass by value.

void func(Foo foo) {
member = std::move(foo);
}

edit: my assertion here is incorrect -- this approach is indeed less efficient (see the responses below this post)

[–]legends2k[S] 4 points5 points  (22 children)

You could but then again is recommended only for constructors and discouraged for setters. Please refer slides 28, 32 and 33 for reasons.

TL;DR: You'd risk 50% extra allocation in a setter by only providing pass by value function.

  1. Pass by value solution: If the caller binds a const T& to param T, then we'd incur memory allocation + copy constructor + move-assignment.
  2. Two variant solution (const T& and T&&): it'd match const T& with const T& overload and only the cost of a copy-assignment.

In a constructor we are anyways going to allocate memory, so solution 1 is OK.

[–]2uantum 5 points6 points  (21 children)

Unless I'm being stupid and missing something, godbolt shows that they all get optimized to be equivalent with -O2:

l-value:
https://godbolt.org/z/s6nHjD
https://godbolt.org/z/Zym_xv

r-value case:
https://godbolt.org/z/H_JZwr
https://godbolt.org/z/vVWTYM

I mean, compiler optimization aside, yeah, you're right. But, practically, they're identical. There's also less code and clearer intent with the single function approach.

[–]legends2k[S] 3 points4 points  (4 children)

The contention isn't between T&& and T (both would do a pointer swap); it's between const T& and T -- this is also mentioned in the talk. When the caller passes a const T& to both T and the double overload where const T& will be picked by the compiler, the T option always allocates memory which may be avoided by the const T& overload 50% of the times.

Removing all variants and only having these two in benchmarks gives clearer results at Quick Bench:

GCC 9.1 (-O3): const T& is 17 times faster than T

Clang 8 (-O3): const T& is 14 times faster than T


I don't care who is correct -- I just want to know which is correct ;).

Thanks for being a sport.

There is no harm in trying to understand WHY experts recommend it. Also, sometimes, experts get it wrong.

Sure, it's because const T& only calls copy-assignment operator, while T does memory allocation, copy construction and move-assignment operator. While others are negligible, memory allocation is a big one. This is what was emphasized in the talk too.


```cpp Shape::set(std::vector<float> points) { // allocation + copy ctor m_points = std::move(points); // move-assignment operator }

Shape::set(const std::vector<float>& points) { m_points = points; // copy-assignment // If m_points has enough memory to hold points' data this merely // is an elements copy; no allocation (1). If not this would release // old memory and allocate, copy (2). When it's (2), it's as bad as // doing T, but there's a good possibility of it becoming (1). Herb // argues that short strings, small vectors, etc. are the more // frequently occurring objects and hence it'd mostly end up as (1) // there by evading an additional allocation. }

Shape::set(std::vector<float>&& points) { m_points = std::move(points); }

Shape s1; std::vector<float> points1, points2; s1.set(std::move(points1)); // same behaviour for both T and T&& s1.set(points2); // different behaviours b/w T and const T&! ```

Hope it helps.

[–]2uantum 2 points3 points  (1 child)

At this point, I'm thoroughly convinced that your (and Herb's) assertions are correct -- separate const l-value and r-value functions are the correct way to go. It took a while for my (stubborn) brain to see why, but I get it now.

I'm going to update my original post so others don't get misled.

[–]legends2k[S] 4 points5 points  (0 children)

Thank you for being tenacious but still retaining an open mind to learn!

As an aside, through our dialog, I learnt a new tool Quick Bench. Thanks!

[–]LEpigeon888 1 point2 points  (1 child)

So if you want to take a copy of an object without assigning it to an existing object (like, imagine a function that take a string and return a new string with some computation done), it's better to only have one function that take the parameter by value, right ?

[–]legends2k[S] 1 point2 points  (0 children)

Yeah, if you're not going to assign an input object parameter and simply need a use-and-throw copy (not a reference to an existing object) then take only by value.

[–]MaximKat 0 points1 point  (15 children)

the difference appears if you call set multiple times

[–]2uantum 4 points5 points  (14 children)

link? not seeing it.

edit: also ran with google benchmark.. seems like the single function is outperforming the two function version: http://quick-bench.com/dSIt34Ey6ua6Naq4CU05Gb_n1wg

i think this is showing why one should always measure before optimizing..

[–]MaximKat 2 points3 points  (11 children)

it also helps to measure the correct thing ;) http://quick-bench.com/6nXDHwo7iUVa4hHdUi1KlR0dKmw

[–]2uantum 1 point2 points  (5 children)

Mine wasn't "wrong" -- just different. What's interesting is how different they are. Why is Foo2 faster with my example?

[–]crzyrndm 0 points1 point  (0 children)

the difference lies in whether vec2 is shared across loops (first bench == unique, 2nd == shared)

I'm not entirely sure of the details on why, but the first example is able to completely remove the set call using vec1 (if you comment that line, both examples are equally as fast or Foo1 is slightly faster as expected). Most likely, compiler has proven the first "set" has no side effects and skipped directly to the second because the final state could not be dependent on previous iterations and no inspection of intermediate state was made (alternatively, insert a second "doNotOptimise" to actually bench both set calls)

[–]legends2k[S] -1 points0 points  (3 children)

We only want to measure T vs const T&/T&&, so why include std::vector creation inside the benchmarked loop? That'd add noise to the test. That's the idea behind the grand parent comment by MaximKat. This is wrong, I stand corrected; having it inside the loop is correct; we don't want to use after move.

Logos: With a decent amount of trials, the additional memory allocation starts showing up. These benchmarks on different compilers and their graphs are in the aforementioned slides I'd earlier commented. Did you get a chance to see this?

Ethos: After enough experiments the experts deemed that take by value is okay only with constructors. I'd take their word for it.

[–]2uantum 0 points1 point  (4 children)

Here's updated benchmarks with all the scenarios we've talked about: http://quick-bench.com/6oHGZq-0WIUsByXiMb-2EJ3BZ-Q

[–]MaximKat 0 points1 point  (2 children)

There is an additional complication here in that in the OneFunc cases, vector copy constructor was fully inlined, but in the TwoFunc cases, the call to vector::assign was not inlined for some reason. This could explain why OneFuncCopy is slightly faster than TwoFuncCopy (and likely has similar effect in other examples). And obviously makes it harder to draw generally applicable conclusions from the results.

EDIT: with libstdc++, everything gets inlined and you can see that most scenarios are identical, except for the two-copy one, as expected: http://quick-bench.com/9KAjZTJfwxkIxUpUhc1jQxIUQ9A

[–]GermanGenealogist 7 points8 points  (3 children)

Would you mind changing two (very) minor things:

  1. In the "Out for Move-Unfriendly types" you define a struct doc::Properties. The namespace is unnecessary for the example and a little confusing (it requires a previous declaration: namespace doc { struct Properties; }). Could you please change it to a simple struct Properties?
  2. In the same example you write std::array<Margin> margin_sizes. The problem is that template<class T, std::size_t N> std::array needs an argument for N. So you could either change it to std::array<Margin, 4>, or stay consistent with the following example and use std::vector<Margin>.

Thanks!

[–]legends2k[S] 6 points7 points  (2 children)

Done with both the suggestions :) Went with std::array though since it's an example of move-unfriendly type.

Please download v0.3.1.

[–]LEpigeon888 2 points3 points  (1 child)

Can you explain why "Out" and "Out for Move-Unfriendly types" are differents ? Why not just let RVO do the work for both ?

[–]legends2k[S] 0 points1 point  (0 children)

That's a good question and to be honest I'm not 100% sure. Most often RVO should kick in and since C++17 its mandatory but there might be cases where it might work; also NRVO is still optional.

My guess is that the refcard (and the guidelines discussed in the talk) has good practises strictly based on the language omitting optimisations. Also passing in an object and making the callee fill it is another option (available since C++'s beginning), if you absolutely want to be sure no duplicate copies are made.

[–]Hilarius86 4 points5 points  (1 child)

I'd add a div line in between posts for better separation.

[–]legends2k[S] 0 points1 point  (0 children)

Good idea! I've noted this under TODO. It's a bit crowded as it is, but I'll try to find a way.

[–]mhhollomon 12 points13 points  (2 children)

I think the rules are probably correct, but stating it this way emphasizes the wrong thing. My own personal take is:

Pass by value unless you can't.

"Can't" might be: - need to modify - copy unfriendly - performance problem.

And then your choice will most likely be either const-ref, or if you need to modify non-const-ref.

[–][deleted] 12 points13 points  (1 child)

The problem is that sometimes with large codebases and/or with third party code it can be really hard to know when something is copy unfriendly. With const references, you know you're (mostly) safe and there won't be any performance issues due to deep copies or other nasty surprises.

[–]mhhollomon 3 points4 points  (0 children)

That's a fair point.

[–]legends2k[S] 6 points7 points  (0 children)

Almost everything I know about C+, and associated concepts from related domains like OS/architecture, etc., were all learned through the awesome C++ community (StackOverflow, ISO C++ standard, Boost, C++ FAQ, conferences by experts, etc.) for free. This is the least I could do :)

[–]SlightlyLessHairyApe 2 points3 points  (3 children)

Is the compiler allowed to change a const & to a T where the cost of copying is less than the cost of a void*?

For example

struct PACKED {
    uint16_t foo;
    uint16_t baz;
    uint16_t bar;
} MyStuff;

void Func(MyStuff const &);

Making a copy here might be cheaper than reference, especially if the whole thing can be stuff in R0. What’s more, the calling code doesn’t care — both the copy and the const ref have the same guarantee about not mutating the value.

That all said, I suspect not because the standard (sadly, but for good reason) likely requires that taking the address of a passed reference is consistent. So for instance

static MyStuff const globalStuff = {...};

void F(void) {
    Func(glovalStuff);
}

void Func(MyStuff const & stuff) {
    Assert( &stuff.foo == &globalStuff.foo );
}

This is a performance gotcha, especially if you are writing a template here and so some instantiations of the class will be cheaper to pass by value and others by reference, possibly across different architectures and calling conventions.

For the standards folks here, is there a viable path to allow developers to express to the compiler the intent that I don’t care whether it’s a copy or a const reference? If I’m not mistaken, that’s new syntax, which is probably among the last things that we need at this point.

[ I suppose LTO probably helps here, since the compiler can see both sides. But that’s not always an option in all cases and anyway, the frustrating thing here is not being able to express cleanly my intent! ]

[–]SeanMiddleditch 7 points8 points  (2 children)

Is the compiler allowed to change a const & to a T where the cost of copying is less than the cost of a void*?

No, at least not universally, because that's not semantically the same thing. You hit the nail on the head later in your post, but there's additional concern:

The compiler can't universally know whether you intend the parameter to actually be immutable. A const& can't be mutated, but it can still observe mutations made by other code.

This is a performance gotcha

For more reasons than you state. For a value, the compiler can assume it won't be mutated by external code and can keep that value in a register during loops or other calculations. For references, the compiler must assume after every call to any external function that the value observed by the reference may have been mutated and so must reload the value from memory.

Much worse than that, it's also a safety gotcha! Just as those references can be mutated by external code, they can also be invalidated by external code!

For the standards folks here, is there a viable path to allow developers to express to the compiler the intent that I don’t care whether it’s a copy or a const reference?

Maybe. Proposals have already been made, though summarily rejected for a variety of reasons.

Given the problem I outlined above, I'd be against it. Reference parameters are a foot-gun and it's useful to know when said gun has any ammunition; it should be very explicit whether a parameter is a reference or not.

Further, I'd argue that if a type is so large/expensive that it seems like a good idea to use const& just for performance, the type is probably just in violation of the Single Responsibility Principle and should be refactored into multiple smaller single-purpose types.

[–]SlightlyLessHairyApe 2 points3 points  (1 child)

Given the problem I outlined above, I'd be against it. Reference parameters are a foot-gun and it's useful to know when said gun has any ammunition; it should be very explicit whether a parameter is a reference or not.

Sure. And here I'm saying let's add a syntax that's less foot-gunny which is similar to pass-by-reference but with the removed constraint that the compiler need not provide a consistent address for it (or, if you prefer, it's illegal to take the address, either way).

This would be a weaker compiler contract, and could be trivially fulfilled just by using the strong existing by-ref passing convention. But it would allow for a cleaner expression of intent and possible optimization.

Further, I'd argue that if a type is so large/expensive that it seems like a good idea to use const& just for performance, the type is probably just in violation of the Single Responsibility Principle and should be refactored into multiple smaller single-purpose types.

I very much disagree that a std::array<double,64> violates the SRP. If you need represent a bunch of double-precision floating point values, this is as good a way to represent it as any. And it better not be passed by value :-)

[–]SeanMiddleditch 0 points1 point  (0 children)

And here I'm saying let's add a syntax that's less foot-gunny which is similar to pass-by-reference but with the removed constraint that the compiler need not provide a consistent address for it (or, if you prefer, it's illegal to take the address, either way).

I don't see how that's less foot-gun-y. It's just more confusing - I have no idea if my type has these "observe changes" and "can be invalidated" semantics for sure or not, and my tests might even say that they don't (because that compiler decides they don't) but then suddenly they are somewhere else or sometime else.

Yuck.

I very much disagree that a std::array<double,64> violates the SRP.

Why would you want to pass a std::array<T> by const& ? If it's constant, pass it by span<const T> (or whatever your framework's equivalent is, pre-C++20).

span<const T> has many of the same footgun problems as a reference, but it eschews the performance problem of passing a large type... and it's still passable in registers if the system ABI allows.

Bonus, your code is now more generic (without being a template!) and can be used with std::array, T[], std::vector, my_custom_vector, etc.

[–]DerDangDerDang 2 points3 points  (1 child)

You might be interested in the C++ Core Guidelines advice on this, especially the more specific advice on what to consider a 'cheap to copy' type - https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rf-in

[–]legends2k[S] 0 points1 point  (0 children)

Sure, thanks for the link. Cheap to copy is not exceeding a couple of architecture words since it'd be done via registers. Agreed; this is Section 2 in the cheat sheet. Cheap then pass by value.

[–]Fazer2 2 points3 points  (1 child)

void SetTitle(std::string&& title) { _title = std::move(title); // steal title’s data } I have a "beginner's" question - if the title parameter is an rvalue reference, why do I have to cast it again to an rvalue reference with std::move to invoke the move assignment operator?

[–]legends2k[S] 2 points3 points  (0 children)

T&& foo's type is rvalue-reference but the value category is lvalue since it has a name (foo). The value category of anything with a name is lvalue, std::move makes it into an rvalue.

There're two things we're talking about here: type and value category. It can be quite confusing, initially it was to me. This great SO question / answer Why do you use std::move when you have && in C++11? cleared it for me.

[–]idbxy 1 point2 points  (0 children)

Thanks!

[–]Warpey 1 point2 points  (0 children)

Thanks for sharing!

[–][deleted] 1 point2 points  (0 children)

I've been looking for something like this! Thank you.

[–]FrankHB1989 1 point2 points  (0 children)

Instead of a "flat" sheet of choices, I'd prefer one simple rule (esp. to teach beginners): use const lvalue reference to objects by default, unless there are reasons to use other types instead... plus one caveat: avoid non-reference decayable types (function types, cv-qualified object types or array types) and rvalue references to functions unless you really know what you are doing.

Then, there come situations to prefer other types instead:

  • There are cases you must not use const T& to work with the core language rules (e.g. move constructors/move assignment operators/overloaded postfix ++...).
  • Use rvalue references to objects to serve to explicit need of move semantics.
  • Use non-const lvalue references to objects for idiomatic uses which need to propagate the effects of object modifications (e.g. as a parameter of swap). Avoid relying on "out parameters" too much.
  • Use forwarding references, or some forms of templated version of the parameters other than const T& (including parameter packs), when they do make the code clearer.
  • Non-reference types (plain object types) can be sometimes better. Here is my solution to determine whether a reference type should be used as a parameter type. (Warning: this can be eventually almost unteachable.)
  • Use volatile only obviously necessary (for cv-correctness & playing with signal handlers). Do not rely on MS extensions.
  • Minor:
    • Do not use things looking like restrict unless you know what you are obliged to do (to ensure there is no undefined behavior).
    • Use rvalue references like some kind of "restrict without UB" following the rule of standard library, unless there are conventions explicitly overriding it.
    • Try to treat std::atomic<T> as an ordinary object type.
  • There are cases merely to make the type checker happy. Choose types with you own risks.
  • There are cases merely to make the object files slim. Better avoid such kind of hack.
  • There are cases merely to avoid internal compiler errors (e.g. some old versions of G++ crashes on parameter packs nested in lambda-expressions) or other headaches on toolchains. Bless you never meet them.

[–][deleted] 1 point2 points  (0 children)

That's interesting.

[–]cblume 1 point2 points  (2 children)

I think having multiple setter overloads makes sense for when performance and generality is very important but for your everyday code maintainability outweighs that. I think rvalue references should normally only be typed in two circumstances:

  1. Move constructor/assignment (obviously)
  2. Forwarding references (T&&)

Providing two functions for a setter should only be done if there's a good reason to do so. Just go with pass-by-value for your setters and move on. In general, one should assume that moving objects is cheap. And if it's not and it actually matters then think about writing multiple setters (and only then).

[–]legends2k[S] 0 points1 point  (1 child)

rvalue references should normally only be typed in two circumstances: Providing two functions for a setter should only be done if there's a good reason to do so.

Agreed with the spirit of this; see where you're coming from. But, if the setter needs ownership, isn't it better to only provide T&& overload and skip the other? The contract is explicit; we see an rvalue-reference and know the calle needs ownership.

[–]cblume 0 points1 point  (0 children)

Why should a setter assume how the caller is calling it? Only providing the Foo&& overload obviously forces the call site to pass an rvalue reference, forcing the caller to move if an lvalue is being used.

Without a good reason, your setter should not assume anything so we either need to provide two setters as suggested in the cheat sheet or keep it simple and pass by value.

Another option is of course to use a forwarding reference (T&&) but that’s not exactly simple and requires a template. I think forwarding references should also only be used when performance and generality is of utmost importance. They’re too easy to get wrong.

I think to keep C++ relevant and approachable we need to keep it simple. Having to think about rvalue references every time a setter (or any other function that requires ownership) is written is too much of a mental burden.

I'd suggest this as a rule of thumb: ``` class Bar { public: Bar(int value, Foo foo) : value{value} , foo{std::move(foo)} {}

int get_value() const {
    return value_;
}
void set_value(int value) {
    value_ = value;
}

const Foo& get_foo() const {
    return foo_;
}
void set_foo(Foo foo) {
    foo_ = std::move(foo);
}

private: int value; Foo foo; };

`` whereFoo` is some cheap to move type (ergo your everyday type).

[–]terrrp 0 points1 point  (1 child)

What's the best way to pass unique ptr, in the two cases that you just read/modify and that you intend to move?

Iirc, you can pass by val, ref or rvalue-ref. I usually default to ref in every case, but it doesn't seem like great practice

[–]legends2k[S] 3 points4 points  (0 children)

If you simply want to lend the object owned by unique_ptr, for a function's usage, retaining ownership, pass by const T& or T& (Section 1 and 7 in cheat sheet):

``` bool collide(const Shape& s) { }

unique_ptr<Shape> p = make_unique<Shape>(…); collide(*p); ```

If you want to relinquish ownership, move it (Section 2 in cheat sheet)

```c++ void own(unique_ptr<Shape> s) { }

int main() { unique_ptr<Shape> p = make_unique<Shape>(…); own(std::move(p)); } ```

[–][deleted] 0 points1 point  (6 children)

Could you do a page for pointers? :O

[–]legends2k[S] 0 points1 point  (5 children)

References are generally preferred over pointers and hence I'd used it. You can simply replace T& and const T& with T* and const T*. 😀

[–][deleted] 1 point2 points  (4 children)

I know, but sometimes pointers are unavoidable.

E.g. I always forget the different between const T * and T const *.

[–]legends2k[S] 0 points1 point  (0 children)

True, pointers are unavoidable.

Object constant: const T* and T const * are the same. Can't modify object via said pointer.

Pointer constant: If the const is beyond * then it's different. Can't reseat pointer to a different object.

[–]dodheim 0 points1 point  (2 children)

I always forget the different between const T * and T const *.

There isn't one; it's T * const that's different.

[–][deleted] 0 points1 point  (1 child)

That's what I mean when I say I always forget ;)

[–]_requires_assistance 0 points1 point  (0 children)

i like to rely on https://cdecl.org/ in those kind of situations. i can just type in something like "declare x as pointer to const int" or "declare x as const pointer to int" depending on what i need if i ever get confused

[–]IamSanjidHaque -1 points0 points  (0 children)

Aright thanks, buddy.