Can't pass temporary object as reference

16,801

Solution 1

By design, C++ only allows a temporary to be passed to a const reference, value, or rvalue reference. The idea is that a function taking a non-const reference parameter is stating that it wants to modify the parameter and allowing it to go back to the caller. Doing so with a temporary is meaningless and most likely an error.

And I don't know what version of g++ you're running. It doesn't work here: http://coliru.stacked-crooked.com/a/43096cb398cbc973

Solution 2

Why is the compiler forbidding my original code?

Because it is forbidden by the Standard:

8.5.3 References 5
...
Otherwise, the reference shall be an lvalue reference to a non-volatile const type (i.e., cv1 shall be const), or the reference shall be an rvalue reference.
[ Example:
double& rd2 = 2.0; // error: not an lvalue and reference not const
...

'

What is it guarding against?

Inadvertently modifying an object that is going to be destructed after the function call.

What is it about each of the three workarounds above that satisfies the compiler?

1 Creates a named object and 3 a copy.
2 Works because the lifetime of the object is simply extended, and changes to it are prevented at the same time.

What would MSVC allow it, but not g++?

Because it is a language extension. Disable it by going to Property Pages->C/C++->Language->Disable Language Extensions and you'll get an error.

Solution 3

Why is the compiler forbidding my original code?

MSVC has an extension that allows temporaries to bind to non-const lvalue-references. Of course this isn't a standard-conforming feature so I would stay away from it to be portable. For example, it doesn't work with the latest versions of GCC and Clang as you've seen.

What is it about each of the three workarounds above that satisfies the compiler?

Back in C++03, expressions could only be lvalues or rvalues. References could only designate the "lvalueness" of an object, and so it was used with the intention of aliasing a preexisting object. By contrast, rvalues don't exist beyond the expression in which they appear. Also, the end result of references was normally to copy or modify the object, and it doesn't make much sense to the language to modify an rvalue like 55 for example.

The rules allow you to bind an rvalue to a lvalue-reference to const, in which case the temporary's lifetime is extended to the lifetime of the reference. When you take an object by value the object is copied.

With C++11 we have rvalue-references and xvalues which were made for the purpose of exchanging ownership. With this is lessens the usefulness of lvalue-references to const. Moreover, taking by-value causes a move if it is an rvalue.

Solution 4

Once you declared the prototype for ProcessFoo as

void ProcessFoo(Foo& foo)

You are conveying your intent as the formal parameter "foo" is subject to modification as it is not being passed by const &.

At the call-site,

ProcessFoo(Foo(42));

Foo(42) is creating a temporary stack object that is not modifiable. It is okay to pass-by-value or pass-by-ref-to-const to a method.

As you listed yourself, satisfying those constraints, makes the compiler happy.

  1. is giving you an object that is not compiler generated and is under your control.
  2. Informs the compiler that the method guarantees const-ness of the object.
  3. Informs the compiler that this (temporary) object is passed by value and hence no issues.
Share:
16,801

Related videos on Youtube

Saurav Seth
Author by

Saurav Seth

Updated on October 15, 2022

Comments

  • Saurav Seth
    Saurav Seth over 1 year

    This is a very minimal example:

    class Foo
    {
    public:
        Foo(int x) {};
    };
    
    void ProcessFoo(Foo& foo)
    {
    }
    
    int main()
    {
        ProcessFoo(Foo(42));
        return 0;
    }
    

    The above compiles fine on Visual Studio, but generates an error on Linux and Mac.

    Compiling the above generates this:

    $ g++ -std=c++11 -c newfile.cpp
    
    newfile.cpp: In function ‘int main()’:
    newfile.cpp:23:23: error: invalid initialization of non-const reference of type ‘Foo&’ from an rvalue of type ‘Foo’
         ProcessFoo(Foo(42));
                           ^
    newfile.cpp:14:6: note: in passing argument 1 of ‘void ProcessFoo(Foo&)’
     void ProcessFoo(Foo& foo)
    

    I've found three workarounds:

    1. Avoid the use of an inline a temp variable for the invocation of ProcessFoo.

    Like this:

    Foo foo42(42);
    ProcessFoo(foo42);
    
    1. ProcessFoo takes a const reference: void ProcessFoo(const Foo& foo)

    2. ProcessFoo just lets Foo get passed by value. void ProcessFoo(Foo foo)

    Why is the compiler forbidding my original code? (What is it guarding against)? What is it about each of the three workarounds above that satisfies the compiler? What would MSVC allow it, but not g++?

    • Cheers and hth. - Alf
      Cheers and hth. - Alf over 9 years
      The whole lvalue/rvalue thing is a bit arbitrary, sort of like our intestines: it works, as a result of evolution, but it's just far too complex and with redundancies and non-functional parts, and brittle. Mainly the explanation is that it's at a very local maximum of utility. Any small change is in the direction of even worse.
    • Jay Miller
      Jay Miller over 9 years
    • Lars Wadefalk
      Lars Wadefalk almost 7 years
      Correct me if I'm wrong here, but I have one HUGE objection here to the standard assumption that it wouldn't make sense to modify the data or access a temporary object by reference: Say I have a container-class that internally reference-counts the data it encapsulates (quite common thing), and if the method then being called with a reference to a temporary object of that class (non-const, taking by reference), do make modifications to the data (or not), then internally in this method creates a new container object (say using a constructor that only "takes over" the source data from the passed
    • Saurav Seth
      Saurav Seth
      @Cheersandhth.-Alf - Greatest comment ever.
  • Cheers and hth. - Alf
    Cheers and hth. - Alf over 9 years
    re " passed to a const reference or a value", surely you meant to include rvalue references
  • dtech
    dtech over 9 years
    @JayMiller - I don't think it is meaningless, you really can't think of any situation you might want to pass an object by reference for a purpose other than modify the object being passed and continue using it in its scope, such as for example because it would be more efficient than to copy, and use and possibly modify that object before you are done with it? I mean it is a temporary that will be accessed nowhere else, I don't see why modifying it should be a problem.
  • dtech
    dtech over 9 years
    I mean for example process(createSomething()) { if (!something.isOK()) fix(something); doSomething(something); } why should you pollute the call location with an identifier for something just for the sake of being able to pass by reference and thus fix it if it is not ok. It would be more convenient use a temporary and pass as a non-const reference.
  • Jay Miller
    Jay Miller over 9 years
    @ddriver the case you mention was the reason for creating rvalue references, where we state as part of the interface that we will be can modify the temporary passed and no one will see the side effects. The language designers felt there were too many ways to make mistakes allowing temporaries to bind to non-const reference. If you take the parameter by value and pass a temporary, most compilers will elide the copy, so there would be no efficiency gain by using a non-const reference.
  • dtech
    dtech over 9 years
    "Inadvertently modifying an object that is going to be destructed after the function call." why would that be a bad thing? The fact the object will be destroyed immediately after the function call I think only limits the possible damage that may come from modifying it.
  • imreal
    imreal over 9 years
    @ddriver Not if the function (that might not have source code available) is meant to output a value through the parameter. An error will help you understand its intent.
  • dtech
    dtech over 9 years
    rvalue references were introduced for the purpose and involve move semantics, and granted, they could be also used in that context I don't think that was their original intent. Move semantics are motivated by the need to reuse objects that transfer responsibilities, but in many cases you might not need that. It is true compilers are free to optimize certain value copies, but really don't see why the standard would not allow temporaries to be passed by reference. The reference will remain valid until the foo returns, and afterwards is no longer used, so I don't see any danger there.
  • dtech
    dtech over 9 years
    What do you mean by "output value through the parameter"? You can do it with a const reference, why not with a non-const one?
  • imreal
    imreal over 9 years
    @ddriver I mean using it as an output parameter, it is the only reason why you should ever use a non-const reference parameter.