yohhoyの日記

技術的メモをしていきたい日記

return文と暗黙のムーブと型変換

プログラミング言語C++において、関数からの return 文と暗黙のムーブと型変換の関係についてメモ。*1

下記コードfunc_implicit_move関数のように return 文にて型変換(unique_ptrshared_ptr)を伴う場合、C++11/14言語仕様によって振る舞いが異なる。この仕様変更はCWG DR 1579による。

  • C++11:変数型と戻り値型が異なる場合、右辺値(rvalue)版のオーバーロード選択は試行されない。unique_ptr型の左辺値(lvalue)をとるshared_ptrコンストラクタは存在せず、該当コードはill-formedとなる。
  • C++14:return 文に変数名のみを記述した場合*2、最初に右辺値(rvalue)版のオーバーロード選択が試行される。この結果unique_ptr型の右辺値(rvalue)をとるshared_ptrコンストラクタが選択され、該当コードはwell-definedとなる。

なおC++11/14によらず、func_explicit_move関数のように明示的な右辺値キャスト(std::move(p))を行えばwell-formedとなる。またfunc_same_type関数のように変数型/戻り値型が一致していれば、C++11/14ともに右辺値(rvalue)オーバーロード選択が行われ、同型のムーブコンストラクタ呼び出しは copy elision 対象となる。

// 暗黙のムーブ
std::shared_ptr<int> func_implicit_move()
{
  std::unique_ptr<int> p{ new int(42) };
  return p;  // C++11: NG / C++14: OK
}

// 明示的なムーブ(右辺値キャスト)
std::shared_ptr<int> func_explicit_move()
{
  std::unique_ptr<int> p{ new int(42) };
  return std::move(p);  // C++11/14: OK
}

// 変数型==戻り値型
std::unique_ptr<int> func_same_type()
{
  std::unique_ptr<int> p{ new int(42) };
  return p;  // C++11/14: OK(copy elision対象)
}

func_implicit_move関数はgcc 5.1.0以降で正しくコンパイル可能だが、Clang 3.8.0(-std=c++14)現在もコンパイルエラーとなる。*3

C++11

右辺値(rvalue)オーバーロード試行は copy elision 対象基準に等しいため、変数型と戻り値型が一致している必要がある。C++11 12.8/p31-32より一部引用(下線部は強調)。

31 When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, (snip)

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function's return value
  • (snip)

32 When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. (snip)

C++14

copy elision 対象基準とは別に、右辺値(rvalue)オーバーロード試行要件が定義されている。C++14 12.8/p32より一部引用(下線部は強調)。なお copy elision 対象基準(12.8/p31)はC++11仕様と同一。

When the criteria for elision of a copy/move operation are met, but not for an exception-declaration, and the object to be copied is designated by an lvalue, or when the expression in a return statement is a (possibly parenthesized) id-expression that names an object with automatic storage duration declared in the body or parameter-declaration-clause of the innermost enclosing function or lambda-expression, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue. If the first overload resolution fails or was not performed, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object's type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. (snip)

ノート:あくまでもソースコード上での右辺値キャスト明示有無の問題であり、func_implicit_move関数とfunc_explicit_move関数で呼び出されるコンストラクタの回数および種別は等しい。またunique_ptrshared_ptr型変換コンストラク*4によりshared_ptr型一時オブジェクトが生成され、同一時オブジェクトからのムーブコンストラクタ呼び出しは copy elision 対象となる。つまり copy elision が無効ならば、関数内で2回のコンストラクshared_ptr(unique_ptr&&)shared_ptr(shared_ptr&&)が呼び出される。

関連URL

*1:通称RVO(return value optimization)/NRVO(named-)と呼ばれる copy elision 対象基準に関連する内容だが、C++11/14いずれの仕様でも型変換を伴う場合は定義上ムーブコンストラクタではないため、該当コンストラクタ呼び出しは copy elision 対象外となる。本文末尾のノートも参照のこと。

*2:厳密には自動記憶域期間(automatic storage duration)をもつ変数名、または関数/ラムダ式のパラメータ名、またはそれら変数名を括弧 () で括っただけもの。

*3:2016年10月現在のClang 4.0.0 HEAD(trunk r284303)ならばC++14仕様通りコンパイルが通る。

*4:異なる型からの変換に用いられる非 explicit なコンストラクタ。http://en.cppreference.com/w/cpp/language/converting_constructor