C++0xのautoキーワードによる型推論

C++0xのautoの紹介

autoキーワードを使って変数を定義すると,コンパイラが初期化値を基に変数の型を推論してくれる.

int f();
auto x1 = 1;    // x1 : int
auto x2 = f();  // x2 : int
auto x3 = 3.14; // x3 : double

auto       &x4 = x1;  // x4 : int&
const auto *x5 = &x1; // x5 : const int*

イテレータのように長い名前の型もautoを使えば簡単に書ける.

// before
for (std::vector<int>iterator it = v.begin(), end = v.end(); it != end; ++it) { ... }

// after
for (auto it = v.begin(), end = v.end(); it != end; ++it) { ... }

以下のような複雑なアレもこんな風に簡単にアレできる.

// before
template<class Iterator>
void f(Iterator it) {
  typename std::iterator_traits<Iterator>::value_type temp = *it;
  ...
}

// after
template<class Iterator>
void f(Iterator it) {
  auto temp = *it;
  ...
}

ラムダ関数も簡単に代入できる.

auto f = [](int n){ std::cout << n << std::endl; };

std::vector<int> v = {1, 2, 3, 4, 5}; // C++0xの初期化リスト
std::for_each(v.begin(), v.end(), f);

autoが使えないケース

autoで変数を定義する時は必ず初期化が必要.

auto a; // NG.初期化されないと型を特定できない.

関数の引数や戻り値の型はautoにはできない.この場合はテンプレートを使おう.

void f(auto x); // NG.引数の型はautoにできない
auto g();       // NG.戻り値の型はautoにできない

autoとポインタと参照

ポインタや参照が絡んできた時の型推論についてまとめてみる.

まずはポインタ.autoによって推論された型がポインタとなる場合は,autoの横に明示的に「*」を記述した場合,または初期化値の型がポインタの場合.

A a;
auto  x1 = a;  // x1 : A
auto  x2 = &a; // x2 : A*
auto *x3 = &a; // x3 : A*
auto *x4 = a;  // エラー.初期化値がポインタ型ではない

A f();
auto  y1 = f(); // y1 : A
auto *y2 = f(); // エラー.初期化値がポインタ型ではない

A* g();
auto  z1 = g(); // z1 : A*
auto *z2 = g(); // z2 : A*

次は参照.autoによって推論された型が参照となる場合は,autoの横に明示的に「&」を記述した場合のみ.

A a;
auto  x1 = a; // x1 : A
auto &x2 = a; // x2 : A&

A f();
auto  y1 = f(); // y1 : A
auto &y2 = f(); // エラー.右辺値(一時オブジェクト)に対して左辺値参照変数を定義することはできない

A& g();
auto  z1 = g(); // z1 : A
auto &z2 = g(); // z2 : A&

autoと基底クラスのポインタで発生する問題

基底クラスのポインタ経由で派生クラスのオブジェクトを参照し,これを用いてautoで定義した変数を初期化しようとすると問題が発生することがある.

class B { public: virtual ~B(){} };
class D : public B {};

D *pd = new D();
auto x1 =  pd; // x1 : D*
auto x2 = *pd; // x2 : D

B *pb = new D();
auto y1 =  pb; // y1 : B*
auto y2 = *pb; // y2 : B (DからBへのスライシングが発生している?)

上のコードをg++ 4.6で実行すると,pbはD型のオブジェクトを指しているにもかかわらず,y2はB型のオブジェクトとなる(typeid(y2) == typeid(B)が真になる).おそらくD型のオブジェクトである*pbのうちB型と共通する部分のみがy2に中途半端にコピーされ,D型の部分のメモリはコピーされず,y2は不完全なオブジェクトとなってしまっているのだろうと考えられる.autoによる型推論は(コンパイル時に決定可能な)静的な型によって行われ,ポインタの指している先のオブジェクトの型までは考慮されないみたいなので注意が必要だ.