Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Return value optimization of tuple/tie

I am looking into return value optimization in the case of tuple/ties and the behavior I observe is not as I expected. In the example below I would expect move semantics to kick in, which it does, but there is one copy operation which remains. The output from the below in optimized is:

Test duo output, non_reference tuple
Default constructor invoked
Parameter constructor invoked
Copy constructor invoked
Move Assignment operator invoked
100

The invocation of the copy constructor in making the tuple inside the function seems unnecessary. Is there any way to remove this? I am using the MSVC 2012 compiler.

#include <iostream>
#include <tuple>

class A
{
public:
     int value;
     A() : value(-1)
     {
         std::cout << "Default constructor invoked" << std::endl;
     }

     explicit A(const int v) : value(v)
     {
         std::cout << "Parameter constructor invoked" << std::endl;
     }

     A(const A& rhs)
     {
         value = rhs.value;
         std::cout << "Copy constructor invoked" << std::endl;
     }

     A(const A&& rhs)
     {
         value = rhs.value;
         std::cout << "Move constructor invoked" << std::endl;
     }

     A& operator=(const A& rhs)
     {
         value = rhs.value;
         std::cout << "Assignment operator invoked" << std::endl;
         return *this;
     }

     A& operator=(const A&& rhs)
     {
         value = rhs.value;
         std::cout << "Move Assignment operator invoked" << std::endl;
         return *this;
     }
 };

 std::tuple<A, int> return_two_non_reference_tuple()
 {
     A tmp(100);

     return std::make_tuple(tmp, 99);
 }

 int main(int argc, char* argv[])
 {

      std::cout << "Test duo output, non_reference tuple" << std::endl;    
      A t3;
      int v1;
      std::tie(t3, v1) = return_two_non_reference_tuple();
      std::cout << t3.value << std::endl << std::endl;

      system("pause");
      return 0;
}
like image 318
thorsan Avatar asked Mar 11 '16 09:03

thorsan


4 Answers

The move constructor will not be called automatically because you are calling

std::make_tuple(tmp, 99);

In this case, tmp is an lvalue. You can use std::move to cast it to an rvalue reference:

return std::make_tuple(std::move(tmp), 99);

This will instruct the compiler to use the move constructor.

like image 79
Marko Popovic Avatar answered Oct 19 '22 16:10

Marko Popovic


The copy occurs here:

std::make_tuple(tmp, 99);

Although you can see that tmp might be able to be directly constructed in the tuple, the copy from tmp to the tuple will not be elided. What you really want is a way to pass in arguments for std::tuple to use to construct its internal A and int objects. There isn't such a thing for std::tuple, but there is a way to achieve the same effect.

Since you only have two types, you could use std::pair. This has a std::piecewise_construct constructor, which takes two std::tuples containing the arguments to pass to the constructors of the internal objects.

 std::pair<A, int> return_two_non_reference_tuple()
 {
     return {std::piecewise_construct, 
             std::make_tuple(100), std::make_tuple(99)};
 }

The cool thing about this solution is that you can still use std::tie at the call site, because std::tuple has an assignment operator from std::pair.

std::tie(t3, v1) = return_two_non_reference_tuple();

As you can see from the output, your copy is gone. It isn't replaced by a move like in the other answers, it's totally removed:

Test duo output, non_reference tuple

Default constructor invoked

Parameter constructor invoked

Move Assignment operator invoked

100

Live Demo

like image 38
TartanLlama Avatar answered Oct 19 '22 16:10

TartanLlama


The copy takes place in return_two_non_reference_tuple().

One way to remove it is to move tmp

std::tuple<A, int> return_two_non_reference_tuple()
{
    A tmp(100);
    return std::make_tuple(std::move(tmp), 99);
}

or another way

std::tuple<A, int> return_two_non_reference_tuple()
{
    return std::make_tuple(A(100), 99);
}
like image 3
Lukáš Bednařík Avatar answered Oct 19 '22 16:10

Lukáš Bednařík


You are not moving tmp:

 A tmp(100);

 return std::make_tuple(std::move(tmp), 99);
like image 1
Simple Avatar answered Oct 19 '22 16:10

Simple