【问题标题】:Multiple return values (structured bindings) with unmovable types and guaranteed RVO in C++17在 C++17 中具有不可移动类型和保证 RVO 的多个返回值(结构化绑定)
【发布时间】:2016-07-14 22:51:28
【问题描述】:

使用 C++ 17,我们将有可能返回不可移动(包括不可复制)类型,例如 std::mutex,通过可以被认为是保证返回值优化 (RVO):Guaranteed copy elision through simplified value categories

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

我的想法是,要让它工作,它需要保证聚合构造函数参数的 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();

【问题讨论】:

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


    【解决方案1】:
    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 中的类成员访问实现了 get_ensured_rvo_strpair 返回值,因此 rvoStr 实际上会从 @987654333 构造@ 那个具体化的临时成员。但在此之前你有一个问题。)

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

    这很好(假设你有一个扣除指南)。聚合初始化不调用many的任何构造函数;它直接使用相应的prvalue初始化器初始化成员。

    【讨论】:

      【解决方案2】:

      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() 的返回是纯右值,则保证省略规则将适用。

      auto rvoStr = get_ensured_rvo_str().first;
      

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

      因此,在任何版本的标准下,rvoStr 的副本都被省略了。

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

      我将假设您已经为编译 return 语句进行了必要的添加。

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

      【讨论】:

      • 除非使用tuple_size 接口,否则它们实际上永远不会被引用,即使那样,它们也会努力假装不是一个(特殊的decltype 规则等等)。
      • @T.C.:等等,他们实际上添加了特殊的decltype 语言,以便它们看起来不像引用?但无论如何,它们“好像”它们是引用,因此不允许结构化绑定实现复制成员或任何东西。这对这个问题来说已经足够好了。
      • @T.C.和尼可波拉斯。谢谢你们!清晰又好。关于.first 我知道我在推动它.. 我们会改为做auto [str,dummy] = get_ensured_rvo_str(); 吗?另外,aggregate typeen.cppreference.com/w/cpp/language/aggregate_initialization 是能够容纳这样的不可移动类型的确切要求吗?
      • 是的。在tuple_* 的情况下,decltype 被定义为返回tuple_element 返回的任何内容,所以std::tuple&lt;int, int&amp;&gt; p(); auto [x, y] = p();decltype(x)intdecltype(y)int&amp;,即使在后台xint&amp;&amp;.
      猜你喜欢
      • 2020-12-27
      • 1970-01-01
      • 2012-11-13
      • 1970-01-01
      • 1970-01-01
      • 2018-09-10
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多