1

I am sorry if this question was asked before or I am missing something trivial, however the following for references is not clear to me. I understand why passing a rvalue instead of lvalue is allowed when the function parameter is not a reference, however I do not understand the reason it is allowed when the function parameter is a const reference (which doesn't make sense to me) but forbidden when passing a usual reference (the logical behavior).

Suppose that I have the following code

struct A
{
    explicit A(std::string s) : name{ s } { };
    A(const A& a) : name{ a.name } {  }
    A& operator=(const A& a) { name = a.name; return *this; }
    A(A&& a) noexcept : name{} { std::swap(name, a.name); }
    A& operator= (A&& a) noexcept { std::swap(name, a.name); return *this; }
    void talk() const{ std::cout << name << " says blablabla.\n"; }
private:
    std::string name;
};

void f(A a) {}
void g(A& a) {}
void h(const A& a) {}

int main()
{
    A a{ "a" };
    f(a);
    f(A{ "temp" });
    g(a);
    g(A{ "temp" }); // Compile error
    h(a);
    h(A{ "temp" });
}

Now, I understand why there implicitly generated overload for A&& for f, however the behavior for A& and const A& is confusing me.

  1. Why does the compiler forbid passing an A&& instead of A& but allows passing an A&& instead of A&?
  2. Is this a bug or a feature?
  3. In case it is a feature, what is the reason for allowing it?

Thanks.

EDIT: The suggested question is not exactly what I asked. My question was, why does it allow a binding of temporary object even when it is const. The linked question asks "why does it forbid when it is not const".

Accepted answer: The answer provided by eerorika in comments to his question makes most sense, which is backwards compability with pre c++11.

  • Does this answer your question? [How come a non-const reference cannot bind to a temporary object?](https://stackoverflow.com/questions/1565600/how-come-a-non-const-reference-cannot-bind-to-a-temporary-object) –  May 18 '20 at 04:23
  • Note that that line is trying to bind a temporary to a non-constant reference. –  May 18 '20 at 04:24
  • I am not sure. My question was why "does it allow the binding" the question is "why does it forbid". – Myrddin Krustowski May 18 '20 at 04:48
  • So you're confused about `h()` not `g()`? –  May 18 '20 at 05:30
  • You could put it that way. I am curious, why allow implicit binding to temporary value at all, no matter it is const or not. Both are potentially dangerous. Of course, you can argue that doing something with const reference inside of a function except reading its value (for example if I use const reference and take its address in a setter function) is a bad idea that should be discouraged anyway, but still... And if you do allow this binding, why allow it only in case of const references. – Myrddin Krustowski May 18 '20 at 05:55
  • If you pass a const ref you are only interested in reading the value, so it doesn't matter if it's an rvalue or lvalue. If you on the other hand pass a non-const ref you need to know if it's an rvalue or lvalue to deal with it properly. – super May 18 '20 at 05:59
  • Not exactly true. Here's (bad) code example: void setA(const A& a) { aPtr = &a; }. – Myrddin Krustowski May 18 '20 at 06:09
  • @RazielMagius You can store a pointer to a local variable as well. That doesn't mean it makes any sense. Just like storing a pointer to a const ref parameter does not make any sense. – super May 18 '20 at 06:29
  • Hmm, it does. If I want increased null safety, I need to use a reference, not a pointer. – Myrddin Krustowski May 18 '20 at 07:48
  • @RazielMagius Sure, but that would still not guard against the referenced object going out of scope. No matter how you turn it you can never be safe from everything, you just have to write correct code. C++ is a language where you are allowed to shoot yourself in the foot. Passing a const ref to avoid a null check and intruducing the possibility of someone passing a temporary seems like a bad trade-off. Storing raw pointers is not generally a good solution, but when it is the best option you just need to make sure you do it properly. – super May 18 '20 at 17:12
  • It's not just to avoid nullcheck. It is to avoid nullcheck at compile time. That can make a difference, sometimes. I agree that working with raw pointers is not very safe, hence I've mentioned that it was a bad example. However, pointers still suffer from the same problem. They have even less safe guards. While, I can prevent passing a reference to temporary object by explicitly deleting the function, I can not do it with a pointer. Anyway, as it has been mentioned before, a reference still can go out of scope. – Myrddin Krustowski May 18 '20 at 19:28
  • As for correct code. Let's agree that you don't always have a control over all parts of the program. – Myrddin Krustowski May 18 '20 at 19:28

2 Answers2

2

It is a way to pass expensive values to functions. It allows binding to temporaries because sometimes results from functions are also expensive, and you don't want to pollute the code by saving every expensive value as a variable... C++ does it for you and that's how we get a temporary.

It adds consistency:

//  Whichever you pick, the code below shall be fine.
//using Value = int;
using Value = std::string;
//using Value = std::vector<std::string>;

Value get_something();
Value process(const Value & text);
void set_something(const Value & name);

set_something(process(get_something()); 

Remember: Back in the day, there was no move-semantics. And even with it, moving around values every time they are passed to a function doesn't sit right with me.

Dragan
  • 66
  • 5
  • I have no problem with move semantics. I just fail to see implicitly overloading functions that accept const references to accept const rvalue achieves what you've mentioned. – Myrddin Krustowski May 18 '20 at 09:29
  • It allows chaining functions "set_something(process(get_something()))" without losing performance. How else are you going to do it? And what do you mean by "implicitly overloading"? – Dragan May 18 '20 at 09:37
  • Hmmm, the fact that you must explicitly delete the && version of function to prevent the behavior I described, implies there is an overload? Anyway, there is no performance benefit, just backwards capability, as function accepting const A& as argument is going to employ copy semantics and move semantics anyway. To get performance benefits, you should overload and write && explicitly or use std::forward. – Myrddin Krustowski May 18 '20 at 13:43
  • Function accepting "const A &" doesn't need to copy or move, it can just access the original value through the passed reference. There is no cost other than passing a reference. – Dragan May 18 '20 at 13:53
  • The example you've provided set_something(process(get_something()) actually implies that there is assignment which implies either copying or moving. – Myrddin Krustowski May 18 '20 at 13:56
1

I do not understand the reason [passing a rvalue instead of lvalue] is allowed when the function parameter is a const reference

This makes copying from rvalues possible using copy constructor. There may be other reasons too, but this is quite a useful feature.

Is this a bug or a feature?

It is intentionally specified in the language.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • But if I pass a const reference I do not want to copy it. I want to pass a reference while protecting it from modification. Constructors are already (unfortunately) a special case, are there any other benefits except saving from typing a couple more lines? – Myrddin Krustowski May 18 '20 at 04:40
  • @RazielMagius If you don't want something to be copied, then don't call a function that copies. Even though copy constructors are special, the less special they are, the better. No need for unnecessary speciality. There may be more benefits, but I won't bother thinking of more. The added simplicity and readability is good enough for me. – eerorika May 18 '20 at 04:42
  • I might be missing something, but except a few lines from writing explicit move constructor or move assignment operators to "make copying from rvalues possible", what other benefits does it provide? Are you saying that is this was not the case, I would have to write both copy constructor and move constructor each time? Or something else? – Myrddin Krustowski May 18 '20 at 04:55
  • calling a functioin with h(const A& a) doesn't copy, and I am not copying it. But it just introduces a potential memory leak. – Myrddin Krustowski May 18 '20 at 04:59
  • 1
    @RazielMagius `except a few lines from writing explicit move constructor or move assignment operators to "make copying from rvalues possible"` You have to remember that copying was a thing even before the language had move constructors. That would have not made copying rvalues possible prior to C++11. And removing the possibility of using copy constructor to copy an rvalue after C++11 would have been a backwards incompatible change, with no benefits. – eerorika May 18 '20 at 10:14
  • @RazielMagius Why would `h(const A& a)` introduce a memory leak? It doesn't allocate any memory. – eerorika May 18 '20 at 10:15
  • Not sure that "leak" is the correct term. Undefined behavior it is. If you don't read it, but for example pass const reference instead of pointer to avoid passing null. But I think that using references is bad idea, since they can go out of scope anyway. Anyway, backwards compability seems to be the correct answer, assuming that it didn't change from pre C++11 either. – Myrddin Krustowski May 18 '20 at 13:32
  • @RazielMagius You are in control of when a reference goes out of scope. Did you mean that the referred object goes out of scope? Yes, that is inherently a problem with indirection. It is not specifically a problem with references because it applies equally to pointers. Regardless, indirection is very powerful tool that makes things possible that wouldn't otherwise be possible. Don't use it when you don't need it. – eerorika May 18 '20 at 13:35