Passing rvalue as reference
Asked Answered
F

4

10

I have some Qt code that I downloaded from my svn repo. It's a while since I worked on it but I am sure it used to compile.

I have a new version of Qt and compiler (to what I had in the last time). My current compiler is: mingw 4.9.2 32-bit.

So here is my problem code:

QByteArray dataBlock = audioTestFile.read(PACKET_SIZE_TO_ENCODE);
// This line is the issue
uint8Vect_t testVect = encodeData(uint8Vect_t(dataBlock.begin(), dataBlock.end()));

Where:

typedef std::vector<uint8_t> uint8Vect_t;

and

uint8Vect_t encodeData(uint8Vect_t &dataBuff);

So you can see here that I have a function encodeData() which takes a parameter uint8Vect_t & (pass by ref). I am passing a temporary variable (an rvalue I think) created using the std::vector constructor (one of which takes two iterators) from the QByteArray dataBlock iterators (which I have tested works).

However, I get the error:

../audioTest/txaudiostream.cpp: In member function 'void CTxAudioStream::playFile()': ../audioTest/txaudiostream.cpp:212:94: error: no matching function for call to 'CTxAudioStream::encodeData(uint8Vect_t)' uint8Vect_t testVect = encodeData(uint8Vect_t(dataBlock.begin(), dataBlock.end())); ^ ../audioTest/txaudiostream.cpp:212:94: note: candidate is: ../audioTest/txaudiostream.cpp:36:13: note: uint8Vect_t CTxAudioStream::encodeData(uint8Vect_t&) uint8Vect_t CTxAudioStream::encodeData(uint8Vect_t &dataBuff) ^ ../audioTest/txaudiostream.cpp:36:13: note: no known conversion for argument 1 from 'uint8Vect_t {aka std::vector}' to 'uint8Vect_t& {aka std::vector&}'

Basically it's saying that I cannot convert from uint8Vect_t to uint8Vect_t&. But if I pass a variable of type uint8Vect_t into the function (rather then the return value of the contructor / temp variable) then this works ok.

I thought in c++11 you can pass rvalues.. but I am obviously missing something here. Can anyone explain:

  1. Why this is wrong?
  2. What is an efficient/elegant (readable) solution?
Flores answered 7/1, 2016 at 12:49 Comment(7)
What was your old compiler? MSVS?Broadside
You cannot pass a temporary (rvalue) by non-const reference. Either pass by value or pass by const reference depending on the semantics you are expecting (ownership, mutability, etc).Lakenyalaker
@Broadside There is a good chance it was, I was messing around with loads of different compilers... but I can't be 100% sure... would there be a difference? surly its syntax is correct or its not? (not being pedantic, its a serious question)Flores
@CoryKramer ahh... it has to be const because (obviously) you can't change it.... so this is not a move semantic as such in this case?... (this is where I get a bit lost)...Flores
The code was wrong, so the fact that it previously compiled told us that the previous compiler was likely to be MSVC (which compiles that particular example of incorrect code).Spelaean
@Flores I added to my answer an explanation.Broadside
@Spelaean and NathanOliver: Yeah, now I understand my error... it must have been msvc. This code always seemed awkward. ThanksFlores
B
18

Your issue is

uint8Vect_t encodeData(uint8Vect_t &dataBuff);

Here you are taking a reference to a uint8Vect_t. That works well with normal variables but uint8Vect_t(dataBlock.begin(), dataBlock.end()) is a temporary object and cannot be bound to lvalue reference.

If encodeData() does not change dataBuff then the simplest solution is to take a const & which can bind to a temproary.

uint8Vect_t encodeData(const uint8Vect_t &dataBuff);

If you have to change the contents of dataBuff then you would have to write another version of encodeData() that takes an rvalue reference

uint8Vect_t encodeData(uint8Vect_t &&dataBuff);

This will allow the function to bind to the temporary vector and you can work on it in the function as you would a normal vector.


I believe the reason you are seeing this is that your old compiler was a version of Microsoft Visual Studio. MSVS has a non standard extension that is on by default that allows temporary objects to bind to a lvalue reference. You can read more about it at: Non-const reference bound to temporary, Visual Studio bug?


Adding this to show you how you could change encodeData() to take an rvalue reference without having to write a new function.

#include <iostream>
#include <vector>

std::vector<int> modify(std::vector<int>& foo)
{
    for (auto & e : foo)
        e *= 2;
    return foo;
}

std::vector<int> modify(std::vector<int>&& foo)
{
    return modify(foo);
}


int main()
{
    std::vector<int> foo = modify({ 1,2,3,4,5 });
    for (const auto & e : foo)
        std::cout << e << " ";
}

Live Example

In the above example modify({ 1,2,3,4,5 }) calls modify(std::vector<int>&& foo) and then in the function foo is an lvaue. We then return the result of passing the "new" lvalue to modify(std::vector<int>& foo) which then returns a modified vector.

Broadside answered 7/1, 2016 at 12:59 Comment(4)
Thanks for the great explanation, +1 ... I thought already move semantic was in play because of c++11... but I had totally forgotten the new syntax as well... thanks v. muchFlores
Thanks for that MSVC info ... that seems to make some sense as I am sure I left this code in working order (plus its fairly fundamental part of my code so was working early on)... but how exactly it worked is odd becuase I mod the values :o .. must just be partly luck.... never a good thing to use in code I hear ; ) Answer goes to you for answering all the parts of the question and also for the MSVC thing...Flores
@Flores Thanks for that. I also added an example of how you could overload encode() to take an rvalue without having to rewrite a bunch of code.Broadside
I have experienced the same in my experiments, but reading this blog it seems that you cannot bind a rvalue even to a const parameter: fluentcpp.com/2022/05/16/…Slew
B
6

When you use

encodeData(uint8Vect_t(dataBlock.begin(), dataBlock.end()))

the vector you pass into the function is a temporary object, and references can't bind to temporary objects.

The simple solution, if the function doesn't modify the argument, is to make it a reference to a constant object:

uint8Vect_t encodeData(uint8Vect_t const& dataBuff);

References to constant objects can bind to temporary objects.

Bog answered 7/1, 2016 at 12:57 Comment(4)
Ok, I think I am getting this now (from your answer and other comments)... the const thing seems obvious now (after explanation!), +1Flores
Just re-checked.... I am modifying the dataBuf, so I need to pass with a move or copy I think.Flores
@Flores Then you have basically two solutions: Create a temporary variable and pass it to the function, or create two overloads of the function, one taking reference to a non-constant object (which can modify the passed argument), and one taking a reference to a constant object (which can't modify the argument).Bog
Thanks very much : )Flores
S
2

What do you want to do with/to the object you are passing in?

When you take it as uint8Vect_t &dataBuff that should mean you want to make lasting modifications to it, which makes no sense if it is a temporary.

When you take it as uint8Vect_t const&dataBuff that should mean you want to copy from it and not modify it, which is probably what you want.

When you take it as uint8Vect_t dataBuff that should mean you need your own local temporary copy of it, to use as you wish and then throw away, and that should be important enough to be worth the cost of copying.

When you take it as uint8Vect_t &&dataBuff that should mean you want to make non lasting modifications (such as content stealing) from a temporary object that the caller is effectively promising to throw away after you are done with it.

That last choice is the one new in C++11 for passing rvalues.

Spelaean answered 7/1, 2016 at 12:56 Comment(1)
Thanks for detailed answer, +1. I am modifying the data (thought I was not, but after a second look I am)... so you're right, const won't work for me, I have to use the move semantic : )Flores
W
0

Return value of any function is an temporary object(rvalue) and you can't pass temporary object as reference.

below code will generate same error as we are trying to pass "saurabh"(temp object) as reference type.

i.e.

void fun(string& name){
  //statements;
}

int main(){
    fun("Saurabh");
    return 0;
}
Wearproof answered 4/4, 2019 at 9:54 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.