C++17 中具有不可移动类型和保证 RVO 的多个 return 值(结构化绑定)
Multiple return values (structured bindings) with unmovable types and guaranteed RVO in C++17
使用 C++ 17,我们将有可能 return 不可移动(包括不可复制)类型,例如 std::mutex
,通过可以被认为是有保证的 return 值优化( 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();
}
我的想法是,要使其起作用,需要在形成 std::tuple
或 many
时保证聚合构造函数参数的 RVO,但不会将其命名为 RVO (NRVO) P0144R2提案中具体不包括?
旁注:P0144R2 特别提到支持仅移动类型:
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();
template<typename T1,typename T2,typename T3>
struct many {
T1 a;
T2 b;
T3 c;
};
auto f(){ return {string(),5.7, false} };
这不会编译。首先你从来没有说过 f
是 return 一个 many
。其次,class 模板参数推导与构造函数一起工作,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
中的 class 成员访问权限具体化了 [=21= 的 pair
return 值],因此 rvoStr
实际上会从该物化临时对象的 first
成员构造而来。但在此之前你就遇到了问题。)
auto get_class_and_mutex(){
return many{SomeClass(),std::mutex(),std::string()};
}
auto [ mtx,sc,str ] = get_class_and_mutex();
这很好(假设你有推导指南)。聚合初始化不调用 many
的任何构造函数;它直接用相应的纯右值初始化器初始化成员。
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);
在处理结构时,x
、y
和 z
将不是引用;它们将是 "refers to" 实际数组成员的内容,但它不是实际引用。要点是 x
、y
和 z
永远不会是任何东西的 副本 。
因此,问题是HIDDEN_VALUE
是否被复制。很明显 HIDDEN_VALUE
是构造的值。因此,如果 f()
的 return 是纯右值,则保证省略规则将适用。
auto rvoStr = get_ensured_rvo_str().first;
表达式 get_ensured_rvo_str()
是一个纯右值。但是,对其应用 .first
的结果是 而不是 纯右值。应用 .first
会强制纯右值(根据保证省略规则)构造一个临时值,并对其应用 .first
。提取的元素是一个 xvalue,将用于复制初始化 rvoStr
.
所以在任何版本的标准下都没有复制到 rvoStr
。
return many{SomeClass(),std::mutex(),std::string()};
...
auto [ mtx,sc,str ] = get_class_and_mutex();
我将假设您已经为编译 return
语句添加了必要的内容。
鉴于此,函数中的构造会直接在return处初始化HIDDEN_VALUE
。并且聚合的每个成员都将直接由 prvalues 初始化,因此不会发生复制。
使用 C++ 17,我们将有可能 return 不可移动(包括不可复制)类型,例如 std::mutex
,通过可以被认为是有保证的 return 值优化( 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();
}
我的想法是,要使其起作用,需要在形成 std::tuple
或 many
时保证聚合构造函数参数的 RVO,但不会将其命名为 RVO (NRVO) P0144R2提案中具体不包括?
旁注:P0144R2 特别提到支持仅移动类型:
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();
template<typename T1,typename T2,typename T3> struct many { T1 a; T2 b; T3 c; }; auto f(){ return {string(),5.7, false} };
这不会编译。首先你从来没有说过 f
是 return 一个 many
。其次,class 模板参数推导与构造函数一起工作,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
中的 class 成员访问权限具体化了 [=21= 的 pair
return 值],因此 rvoStr
实际上会从该物化临时对象的 first
成员构造而来。但在此之前你就遇到了问题。)
auto get_class_and_mutex(){ return many{SomeClass(),std::mutex(),std::string()}; } auto [ mtx,sc,str ] = get_class_and_mutex();
这很好(假设你有推导指南)。聚合初始化不调用 many
的任何构造函数;它直接用相应的纯右值初始化器初始化成员。
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);
在处理结构时,x
、y
和 z
将不是引用;它们将是 "refers to" 实际数组成员的内容,但它不是实际引用。要点是 x
、y
和 z
永远不会是任何东西的 副本 。
因此,问题是HIDDEN_VALUE
是否被复制。很明显 HIDDEN_VALUE
是构造的值。因此,如果 f()
的 return 是纯右值,则保证省略规则将适用。
auto rvoStr = get_ensured_rvo_str().first;
表达式 get_ensured_rvo_str()
是一个纯右值。但是,对其应用 .first
的结果是 而不是 纯右值。应用 .first
会强制纯右值(根据保证省略规则)构造一个临时值,并对其应用 .first
。提取的元素是一个 xvalue,将用于复制初始化 rvoStr
.
所以在任何版本的标准下都没有复制到 rvoStr
。
return many{SomeClass(),std::mutex(),std::string()}; ... auto [ mtx,sc,str ] = get_class_and_mutex();
我将假设您已经为编译 return
语句添加了必要的内容。
鉴于此,函数中的构造会直接在return处初始化HIDDEN_VALUE
。并且聚合的每个成员都将直接由 prvalues 初始化,因此不会发生复制。