具有不可移动类型的多个返回值(结构化绑定)和C++中保证的RVO 17

Joh*_*erg 13 c++ aggregate rvo c++17

使用C++ 17,我们将有可能返回不可移动(包括不可复制)类型,例如std::mutex,通过可以被认为是有保证的返回值优化(RVO):通过简化的值类别保证复制省略:

struct nocopy { nocopy(nocopy&) = delete; nocopy() = default; };
auto getRVO(){
    return nocopy();
}
Run Code Online (Sandbox Code Playgroud)

我们还将具有结构化绑定,允许:

tuple<T1,T2,T3> f();
auto [x,y,z] = f();
Run Code Online (Sandbox Code Playgroud)

或(这里也使用我对构造函数的特征模板参数推导的理解)

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();
Run Code Online (Sandbox Code Playgroud)

但这些功能是否可以实现这样的功能呢?

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();
}
Run Code Online (Sandbox Code Playgroud)

我的想法是,为了这个工作,它会在需要保证形成的集合体构造函数的参数RVO std::tuplemany,但不会是被命名为RVO(NRVO),这是特别不包括在P0144R2建议?


附注:P0144R2特别提到支持仅移动类型:

2.6仅移动类型

仅支持仅移动类型.例如:

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

T.C*_*.C. 10

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

这不会编译.首先你永远不会说f是回归many.其次,类模板参数推导与构造函数一起工作,唯一的构造函数many是隐式声明的默认,复制和移动构造函数.

你需要一个指南:

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

这也不起作用.nocopy()被物化为一个临时的,它绑定到pair构造函数的引用参数,然后它尝试从它移动并失败.不允许或允许任何暂时的省略.

(当然,正如Nicol Bolas在他的回答中指出的那样,类成员访问get_ensured_rvo_str().first实现了pair返回值get_ensured_rvo_str,所以rvoStr实际上将从该first物化临时的成员构造移动.但是在这之前你有一个问题.)

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

这很好(假设您有扣除指南).聚合初始化不会调用任何构造函数many; 它使用相应的prvalue初始化程序直接初始化成员.


Nic*_*las 6

结构化绑定被定义为在提取对单个值的引用或伪引用的基础上工作.也就是说,如果你这样做:

auto [x,y,z] = f();
Run Code Online (Sandbox Code Playgroud)

你得到的是这样的事情:

auto HIDDEN_VALUE = f();
auto &x = get<0>(HIDDEN_VALUE);
auto &y = get<1>(HIDDEN_VALUE);
auto &z = get<2>(HIDDEN_VALUE);
Run Code Online (Sandbox Code Playgroud)

处理结构时x,y和,z不会被引用; 它们将是"引用"实际数组成员的东西,但它不是实际的引用.重点是x,y并且z永远不会复制任何东西.

因此,问题是是否HIDDEN_VALUE被复制.很明显,HIDDEN_VALUE价值构建.因此,如果返回f()是prvalue,那么保证省略的规则将适用.

auto rvoStr = get_ensured_rvo_str().first;
Run Code Online (Sandbox Code Playgroud)

表达式get_ensured_rvo_str()是prvalue.但是,适用.first它的结果不是 prvalue.施加.first强制prvalue(在保证的省略规则下)构造一个临时的,并.first应用于它.提取的元素(xvalue)将用于复制初始化rvoStr.

所以在没有版本的标准下,副本被rvoStr省略了.

return many{SomeClass(),std::mutex(),std::string()};
...
auto [ mtx,sc,str ] = get_class_and_mutex();
Run Code Online (Sandbox Code Playgroud)

我将假设你已经return为编译语句做了必要的补充.

鉴于此,函数中的构造将直接初始化HIDDEN_VALUE返回站点.并且聚合的每个成员将由prvalues直接初始化,因此不会发生复制.