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

时间:2016-07-14 22:51:29

标签: c++ aggregate c++17 rvo

使用C ++ 17,我们可以通过可以被认为是保证返回值优化(RVO)的方式返回不可移动的(包括不可复制的)类型,例如std::mutexGuaranteed copy elision through simplified value categories:< / p>

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

我们还会structured bindings,允许:

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

或(此处也使用我对功能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();

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

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();
}

我的想法是,为了实现这一点,在形成std::tuplemany时需要保证聚合构造函数参数的RVO,但不会命名为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();

2 个答案:

答案 0 :(得分:9)

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

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

你需要一个指南:

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());
}

这也不起作用。 nocopy()具体化为绑定到pair构造函数的引用参数的临时值,然后该构造函数尝试从中移动并失败。不允许或允许任何暂时的省略。

(当然,正如Nicol Bolas在回答中指出的那样,get_ensured_rvo_str().first中的班级成员访问权限实现了pair的{​​{1}}返回值,因此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(); 的任何构造函数;它使用相应的prvalue初始化程序直接初始化成员。

答案 1 :(得分:6)

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

auto [x,y,z] = f();

你得到的是这样的事情:

auto HIDDEN_VALUE = f();
auto &x = get<0>(HIDDEN_VALUE);
auto &y = get<1>(HIDDEN_VALUE);
auto &z = get<2>(HIDDEN_VALUE);

处理结构时,xyz将不会被引用;它们将是“引用”实际数组成员的东西,但它不是实际的引用。重点是xyz永远不会是副本

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

auto rvoStr = get_ensured_rvo_str().first;

表达式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();

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

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