3

I think that constructor with universal reference parameter has better performance than the one without reference.

From cppreference (https://en.cppreference.com/w/cpp/utility/functional/function/function), we can see that the template constructor for std::function is not using reference.

template< class F > function( F f );

Is it a mistake? If not, why the standard doesn't requires the constructor use universal reference?

EDIT:

Let's think about two cases.

  1. F is a built-in type, here a function pointer whose size is 4 bytes.

Using universal reference:

lvalue case: 4 bytes are copied.

rvalue case: 4 bytes are copied. (Will you apply a std::move to built-in type?)

Passing by value:

all cases: 4 bytes are copied.

  1. F is a type with non-trivial copy constructor, ex. a lambda type which captures a std::string by value. This example stands for most cases, do you agree?

Using universal reference:

lvalue case: copy constructor is invoked once

rvalue case: move constructor is invoked once

Passing by value:

lvalue case: a move and a copy

rvalue case: a move

Is this classification complete? If so, we can run into the conclusion that universal reference is no worse then passing by value. Then it returns to the original question.

EDIT again:

Maybe lambdas without captures are the most common cases, where passing by value is actually passing nothing while passing by reference is passing a pointer. This may be the key. LWG 2774 is related to this question. See it in comments.

jerry_fuyi
  • 483
  • 4
  • 12
  • In this question (https://stackoverflow.com/questions/21605579/how-true-is-want-speed-pass-by-value/21605827?noredirect=1#comment100876008_21605827), I commented that const lvalue reference and rvalue reference constructors together provides at least same performance with respect to the non-reference one. Since universal reference can handle both lvalue and rvalue references, why not use it? – jerry_fuyi Jul 25 '19 at 10:44
  • It is specified to construct from `move(f)`. So there is no copy. – L. F. Jul 25 '19 at 10:45
  • "*I commented that const lvalue reference and rvalue reference constructors together provides at least same performance with respect to the non-reference one*" - you used `std::string` as an example. It not necessarily holds true for every type. – Fureeish Jul 25 '19 at 10:46
  • 1
    @L.F. Whether `move` is used on the argument `f` is irrelevant to whether the initialization of `f` involves a copy construction. – Max Langhof Jul 25 '19 at 10:46
  • @MaxLanghof ... but relevant to whether the construction of the stored object inside involves a copy. – L. F. Jul 25 '19 at 10:47
  • @L.F. ...which is not what the question is about (because that aspect is the same regardless of how you take the argument). – Max Langhof Jul 25 '19 at 10:48
  • @MaxLanghof ... but which is probably why the OP thinks "that constructor with universal reference parameter has better performance than the one without reference." – L. F. Jul 25 '19 at 10:49
  • The first answer of the question about passing by value suggests that passing by value is quicker than using const lvalue reference constructor only in rvalue case and almost same in lvalue case but it still involve an extra move operation although it is O(1). – jerry_fuyi Jul 25 '19 at 10:50
  • @jerry_fuyi I believe the compiler is capable of eliding the move operation. – L. F. Jul 25 '19 at 11:00
  • @L. F. I also believe. The compiler can event elide a copy constructor invocation. So did we reach the conclusion that passing by value and universal reference have the same performance? (My question is edited) – jerry_fuyi Jul 25 '19 at 11:12
  • @jerry_fuyi I have the slight feeling that you are underestimating the optimizing ambition of the compiler. Usually templates and references are elided altogether with optimization turned on. – L. F. Jul 25 '19 at 11:14
  • @L. F. I understand your words but couldn't get your point. The question is about the comparison between passing by value and universal reference. What we should concern is the difference of optimization for both cases, not how great the optimization is. – jerry_fuyi Jul 25 '19 at 11:19
  • @jerry_fuyi "What we should concern about is the difference of optimization for both cases, not how great the optimization is." I don't understand this sentence. The difference of optimization is not how great the optimization is? – L. F. Jul 25 '19 at 11:24
  • 1
    This is [LWG 2774](https://wg21.link/lwg2774). – Barry Jul 25 '19 at 15:41
  • @Barry Though probably overlooked, I don't think it is a defect. Actually constructors and assignment operators do have quite subtle differences, see the [analysis](https://stackoverflow.com/a/53825424/2307646) here. – FrankHB Jul 26 '19 at 14:13

2 Answers2

5

Because that constructor moves its argument, accepting a reference is pointless. This comes down to the usual advice on when to take values.

If you pass a primitive, like an int, passing by reference is a pessimisation. If you pass a complex type, like a std::string, you can already std::move it into the argument (and, if you don't, then that's because you wanted a copy anyway). You get the best of both worlds.

// Bog-standard choice between value and ref; original value only observed

void foo(const int& x) { cout << x << '\n'; }
void foo(const int x) { cout << x << '\n'; }  // Better!

void foo(const std::string& x) { cout << x << '\n'; }  // Better!
void foo(const std::string x) { cout << x << '\n'; }


// When we want to store

void storeACopy(int);
void foo(const int& x) { storeACopy(x); }
void foo(const int x) { storeACopy(x); }  // Better!

void storeACopy(std::string);
void foo(const std::string& x) { storeACopy(x); }  // Meh
void foo(std::string x) { storeACopy(std::move(x)); }  // Cheap!


// So, best of both worlds:
template <typename T>
void foo(T x) { storeACopy(std::move(x)); }

// This performs a copy when needed, but allows callsite to
// move instead (i.e. total flexibility) *and* doesn't require silly
// refs-to-primitives

It also signals to the call site that the original object will not be modified unless you specifically yield ownership with std::move.

If instead the function took a reference, okay you'd potentially save one move if you want to yield. But, moves are supposed to be super-cheap so we're not concerned about that. And if you didn't want to yield then suddenly you have to go through the rigmaroll of an object copy at the callsite. Ugh!

This pattern is key to making the most out of move semantics.

Lightness Races in Orbit
  • 378,754
  • 76
  • 643
  • 1,055
  • So is passing by values always better than universal reference? If so, what's the meaning of universal reference? – jerry_fuyi Jul 25 '19 at 10:53
  • 3
    @jerry_fuyi you seem to have a habit of adding *always* where it shouldn't be added. How did you deduce that universal reference is **always** the best? – Fureeish Jul 25 '19 at 10:55
  • @jerry_fuyi I agree with Fureeish - you need to be really careful about reading advice for a certain scenario, then imagining that it had the word "always" in it. There is no "always". You use the right tool for the job. A universal reference could be useful - it just isn't _in this case_ because the function body is always going to move its argument away. – Lightness Races in Orbit Jul 25 '19 at 10:57
  • @Fureeish There's a question mark at the end of that sentence. – jerry_fuyi Jul 25 '19 at 10:58
  • 1
    @jerry_fuyi There's also a "so" at the start of it ;) – Lightness Races in Orbit Jul 25 '19 at 10:58
  • 1
    @jerry_fuyi there is also "*so*" at the beginning which implies that you deduce the following assertion. – Fureeish Jul 25 '19 at 10:58
4

I think that constructor with universal reference parameter has better performance than the one without reference.

Why do you think that? Have you measured? What if we're talking about passing int? Reference (universal / forwarding or not) is not always a boost for performance.

Is it a mistake?

No, it's the standard convention to pass function objects by value. They are meant to be cheap to copy and when implementing own function objects, you should keep that in mind.

If not, why the standard doesn't requires the constructor use universal reference?

Ultimately, why would it? :)

Fureeish
  • 12,533
  • 4
  • 32
  • 62
  • https://stackoverflow.com/questions/21605579/how-true-is-want-speed-pass-by-value/21605827?noredirect=1#comment100876008_21605827 Below first answer there is my comment, which analyzed the performance. Is it correct? – jerry_fuyi Jul 25 '19 at 10:47
  • @jerry_fuyi `std::string` is not the only type in `C++`. Don't assume that when something holds true for it, it holds true for other types. – Fureeish Jul 25 '19 at 10:49
  • Is it true that my comment holds true for all types using the pimpl idiom? – jerry_fuyi Jul 25 '19 at 10:56
  • 1
    @jerry_fuyi no, since pimpl idiom does not indicate that the type is either cheap or expensive to either move or copy. You can't generalise it like that. – Fureeish Jul 25 '19 at 10:57
  • The "standard convention to pass function objects by value" isn't even completely followed by `std::function` itself. Consider its assignment operator. – Barry Jul 25 '19 at 15:42