web-dev-qa-db-ja.com

C ++ 17で移動不可能な型と保証されたRVOを持つ複数の戻り値(構造化バインディング)

C++ 17では、保証された戻り値の最適化(RVO)と考えることができるものを介して、std::mutexなどの移動できない(コピーできないものを含む)タイプを返す可能性があります。 簡略化された値による保証されたコピーの省略)カテゴリ

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

構造化バインディング もあり、次のことが可能になります。

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

または(ここでも機能の私の理解を使用して コンストラクターのテンプレート引数の推論

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移動専用タイプ

移動専用タイプがサポートされています。例えば:

struct S { int i; unique_ptr<widget> w; };
S f() { return {0, make_unique<widget>()}; }
auto [ my_i, my_w ] = f();
13
Johan Lundberg
_template<typename T1,typename T2,typename T3>
struct many {
  T1 a;
  T2 b;
  T3 c;
};
auto f(){ return {string(),5.7, false} };
_

これはコンパイルされません。まず、fmanyを返すことだと言ったことはありません。次に、クラステンプレートの引数の推定はコンストラクターで機能し、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_str_のpair戻り値を具体化するので、rvoStrは実際、そのマテリアライズされた一時的なfirstメンバーから構築されて移動されます。しかし、ここではそのかなり前に問題があります。)

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

これは問題ありません(控除ガイドがあると仮定します)。集計の初期化では、manyのコンストラクターは呼び出されません。対応するprvalue初期化子を使用してメンバーを直接初期化します。

10
T.C.

構造化バインディング は、個々の値への参照または疑似参照の抽出に基づいて機能するように定義されています。つまり、これを行う場合:

_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);
_

構造体を扱う場合、xy、およびzは参照ではありません。それらは実際の配列メンバーを「参照」するものになりますが、実際の参照ではありません。重要な点は、xy、およびzは決してコピーではないということです。

そのため、問題は_HIDDEN_VALUE_がコピーされるかどうかです。そして、_HIDDEN_VALUE_が価値構築されていることは明らかです。したがって、f()の戻り値がprvalueである場合、保証された省略のルールが適用されます。

_auto rvoStr = get_ensured_rvo_str().first;
_

get_ensured_rvo_str()はprvalueです。ただし、それに_.first_を適用した結果は、notprvalueになります。 _.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によって直接初期化されるため、コピーは行われません。

6
Nicol Bolas