How to return a tuple of strings without temporary extra copies in C++?

52 Views Asked by At

My question is similar in spirit to Avoid extra copy when creating a tuple from return values in C++, but since I did not understand the answer at all and because I am talking about the standard types (i.e. they already have move constructors) I will ask it anyway.

Let us assume c++20.

Consider the following code:

#include <iostream>
#include <tuple>
using namespace std;

static tuple<string> get_tuple()
{
    string arg = "This is a long string allocated on the heap";
    return { arg };
}

void main()
{
    auto [x] = get_tuple();
    cout << "x: " << x << endl;
}

Running this code in Visual Studio 2022 debugger shows that the string destructor is called twice and both times it deallocates some memory. I.e. none of the cases is that of an empty string.

It is my understanding that with the advent of the move constructor it should be possible to avoid the temporary extra copy.

This is indeed the case when returning the string directly:

#include <iostream>
using namespace std;

static string get_tuple()
{
    string arg = "This is a long string allocated on the heap";
    return arg;
}

void main()
{
    auto x = get_tuple();
    cout << "x: " << x << endl;
}

But it does not work with the tuple (nor does it work with the pair).

What am I missing? Why it does not work with the tuple? Is there a way to exliminate the temporary extra copy while still returning a tuple or something like that?

1

There are 1 best solutions below

0
Miles Budnek On BEST ANSWER

Simply std::move the string into the tuple:

static std::tuple<std::string> get_tuple()
{
    std::string arg = "This is a long string allocated on the heap";
    return { std::move(arg) };
}

Demo using a non-copyable type


This is necessary since the automatic conversion to rvalue is only performed on the object returned. In this case, arg is not the object being returned; an anonymous std::tuple<std::string> is. If you want arg to be moved, you must explicitly cast it to an rvalue using std::move.


Note that this does still require the object in the tuple to be move-constructed, but that's usually fine in most cases. The answer you linked in your question shows how to deal with the case where the object in the tuple can be neither copied nor moved, which is pretty rare.