Johan Lundberg Johan Lundberg - 4 months ago 13
C++ Question

Multiple return values (structured bindings) with unmovable types or guaranteed RVO in C++17

With C++ 17, we will have the possibility to return unmovable (including uncopyable) types such as

std::mutex
, via what can be thought of as guaranteed return value optimization (RVO): Guaranteed copy elision through simplified value categories:

struct nocopy { nocopy(nocopy&) = delete; nocopy() = default; };
auto getRVO(){
return nocopy();
}


We will also have structured bindings, allowing:

tuple<T1,T2,T3> f();
auto [x,y,z] = f();


or (here also using my understanding of the feature template argument deduction for constructors)

template<typename T1,typename T2,typename T3>
struct many {
T1 a;
T2 b;
T3 c;
};
// (Original questions missed 'many' on the next line. Thanks, T.C.)
auto f(){ return many{string(),5.7, false} };
auto [x,y,z] = f();


But do these features compose to enable something like this?

auto get_ensured_rvo_str(){
return std::pair(std::string(),nocopy());
}

auto get_class_and_mutex(){
return many{SomeClass(),std::mutex(),std::string()};
}

int main(){
auto rvoStr = get_ensured_rvo_str().first;
auto [ mtx,sc,str ] = get_class_and_mutex();
}


My thinking is that for this to work, it would required guaranteed RVO of the aggregate constructor arguments at forming
std::tuple
or
many
, but wouldn't that be named RVO (NRVO) which is specifically not included in the P0144R2 proposal?




Side note: P0144R2 specifically mentions that move-only types are supported:


2.6 Move-only types



Move-only types are supported. For example:

struct S { int i; unique_ptr<widget> w; };
S f() { return {0, make_unique<widget>()}; }
auto [ my_i, my_w ] = f();


Answer
template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};
auto f(){ return {string(),5.7, false} };

This won't compile. First you never said f is to return a many. Second, class template argument deduction work with constructors, and the only constructors of many are the implicitly declared default, copy and move constructors.

You need a guide:

template<class T1, class T2, class T3>
many(T1, T2, T3) -> many<T1, T2, T3>;
auto get_ensured_rvo_str(){
    return std::pair(std::string(),nocopy());
}

This doesn't work either. nocopy() is materialized into a temporary that is bound to the reference parameter of pair's constructor, which then attempts to move from it and fails. No elision of that temporary is possible or allowed.

(Of course, as Nicol Bolas points out in his answer, the class member access in get_ensured_rvo_str().first materializes the pair return value of get_ensured_rvo_str, so rvoStr would in fact be moved constructed from the first member of that materialized temporary. But here you have a problem well before that.)

auto get_class_and_mutex(){
    return many{SomeClass(),std::mutex(),std::string()};
}
auto [ mtx,sc,str ] = get_class_and_mutex();

This is fine (assuming you have a deduction guide). Aggregate initialization doesn't call any constructor of many; it initializes the members directly with the corresponding prvalue initializer.