rvalue reference v3
右辺値参照(rvalue reference)の導入に伴い、コンストラクタとコピーの挙動が変わります。
#include <iostream>
#include <type_traits>
using namespace std;
struct Noisy {
Noisy() { cout << "ctor Noisy()\n"; }
~Noisy() { cout << "dtor ~Noisy()\n"; }
Noisy(const Noisy&) { cout << "ctor Noisy(const Noisy&)\n"; }
Noisy& operator=(const Noisy&) { cout << "copy Noisy(const Noisy&)\n"; return *this; }
// -------- C++11 から、右辺値参照を引数とする コンストラクタ/コピーが追加された
Noisy(Noisy&&) { cout << "ctor Noisy(Noisy&&)\n"; }
Noisy& operator=(Noisy&&) { cout << "copy Noisy(Noisy&&)\n"; return *this; }
};
struct NoisyInt {
Noisy noisy;
int n = 123;
};
int main() {
NoisyInt src;
NoisyInt dst = move(src);
dst = NoisyInt();
}
コンストラクタ/コピーの挙動をモニタするNoisyをメンバに持つNoisyIntに対し、右辺値参照を引数とするコンストラクト/コピーを行っています。VC++12(現VS2013)では
ctor Noisy() ctor Noisy(const Noisy&) ctor Noisy() copy Noisy(const Noisy&) dtor ~Noisy() dtor ~Noisy() dtor ~Noisy()
とプリントされるのに対し、Torinoでは
ctor Noisy() ctor Noisy(Noisy&&) ctor Noisy() copy Noisy(Noisy&&) dtor ~Noisy() dtor ~Noisy() dtor ~Noisy()
となります。つまり、右辺値参照を引数としたコンストラクト/コピーが行われた際、内包するメンバそれぞれに対しても(正しく)右辺値参照コンストラクト/コピーが行われます。
reference qualifier
右辺値/左辺値のどちらに対して適用されたかによって、メンバ関数を呼び分けられるようになります。メンバ関数宣言のおしりに&を付けると左辺値、&&を付けると右辺値です。
#include <iostream>
using namespace std;
struct X {
void f() & { cout << "L-value\n"; }
void f() && { cout << "R-value\n"; }
};
int main() {
X x;
x.f(); // L-value
X().f(); // R-value
}
return type deduction
C++11から、関数の戻り型を後置する書式が追加されました。
// return type を後置する
auto plus(int x, int y) -> int {
return x + y;
}
関数テンプレートだと引数しだいで戻り型がころころ変わりうるため、decltypeが導入されています。
template<typename T, typename U>
auto plus(const T& t, const U& u) -> decltype(t+u) {
return t + u;
}
...
auto x = plus(10, 4); // int x = 14
auto y = plus(string("じゅう"), "よん"); // string y = "じゅうよん"
Torinoに追加されたreturn type deductionは関数内のreturn 式;に基づいて戻り型を推論してくれるので、"-> 型"を省略できます。
template<typename T, typename U>
auto plus(const T& t, const U& u) { // 戻り型を推論してくれる!
return t + u;
}
...
auto x = plus(10, 4); // int x = 14
auto y = plus(string("じゅう"), "よん"); // string y = "じゅうよん"
ただし、これ"だけ"では期待する型を返してくれないことがあります。
template<typename Container>
auto zeroth(Container& c) {
return c[0];
}
...
std::vector<int> vi = { 10, 20, 30 };
++zeroth(vi); // 先頭要素をインクリメント...できない
ダメなんですね、return型から推論される型はint&ではなく、intだと解釈されてしまうのです。
この問題を解決すべく、decltype(auto)が用意されています。
template<typename Container>
decltype(auto) zeroth(Container& c) {
return c[0]; // Container::operator[]() の型を"そのまま"返す
}
...
std::vector<int> vi = { 10, 20, 30 };
++zeroth(vi); // 先頭要素をインクリメントする!
