はじめに
auto、便利ですね。多めに使うだけでモダンなC++っぽくなり、俺TUEEEE感が出ます。
ただ、C++03時代には型にconstやら参照をそれなりに気を遣って付けていたので、autoを使うと一抹の不安がよぎります。
うわっ・・・私のauto、copyしすぎ・・・?
auto
だけで良かったっけ? 参照とか、constとか、どうすんだっけって悩むのは私だけでしょうか...
基本的には先人が驚きが少なくなる仕様としてくれているはずなのですが、そこはC++、どんな落とし穴があるのか分からないため、ざらっと確認してみます。
仕様の参照元
auto型推論の情報源としては、Effective Modern C++および光成さんの勉強会資料を参照しました。
ここでは仕様の詳細には踏み込まず、まとめサイトレベルの結論だけまとめてしまおうと思います。
テンプレート型推論
autoの型推論は、テンプレートの型推論と同じになっています。なので、まずはテンプレートの型推論結果をみてみましょう。
説明のためのお約束
// こんなテンプレートがあって、
template<typename T> void func(ParamType param);
// こう呼んだとします
func(expr);
ここでParamType
は説明用のメタな文字です。実際にはたとえば T
とか、装飾されたconst T&
とかがはいります。
たとえばこんなふう:
template<typename T> void func(const T& param);
func(expr);
expr
もまた、説明用のメタな文字です。実際には、42
とか変数x
とかが入ります。
たとえばこんなふう:
template<typename T> void func(const T& param);
int x = 10;
func(x);
以上のメタな文字ParamType
と expr
をいろいろ変えてみて、T
とParamType
が結局何になるのかの一覧表を示します。
右辺値は目立つよう(int)rvalue としています。また、ちょっと注意が必要な欄は赤にしています。
ついでにParamType
欄に、つけた装飾が持つ意味を一言書いてみました。
ParamType | expr | T | deduced ParamType | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
T param 「全部コピーして所有する」 |
(int)lvalue | int | int param | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(const int)lvalue | int | int param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(int&)lvalue | int | int param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(const int&)lvalue | int | int param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(int*)lvalue | int* | int* param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(const int*)lvalue | const int* | const int* param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
(int)rvalue | int | int ※コピーコンストラクタが呼ばれるかどうかは時と場合による T* param |
「ポインタしか受け取らない」 (int)lvalue |
ポインタでないのでコンパイルエラー
|
(const int)lvalue |
ポインタでないのでコンパイルエラー
|
(int)lvalue |
ポインタでないのでコンパイルエラー
|
(const int&)lvalue |
ポインタでないのでコンパイルエラー
|
(int*)lvalue |
int |
int* param
|
(const int*)lvalue |
const int |
const int* param
|
(int)rvalue
|
ポインタでないのでコンパイルエラー
|
const T* param |
「ポインタしか受け取らない。受け取ったポインタが指す先は変更しない。」 (int)lvalue |
ポインタでないのでコンパイルエラー
|
(const int)lvalue |
ポインタでないのでコンパイルエラー
|
(int)lvalue |
ポインタでないのでコンパイルエラー
|
(const int&)lvalue |
ポインタでないのでコンパイルエラー
|
(int*)lvalue |
int |
const int* param
|
(const int*)lvalue |
int |
const int* param
|
(int)rvalue |
ポインタでないのでコンパイルエラー
|
T& param |
「コピーしない。あるがまま参照する。受け取ったパラメータは変更するかも。」 (int)lvalue |
int |
int& param
|
(const int)lvalue |
const int |
const int& param
|
(int&)lvalue |
int |
int& param
|
(const int&)lvalue |
const int |
const int& param
|
(int*)lvalue |
int* |
int*& param
|
(const int*)lvalue |
const int* |
const int*& param
|
(int)rvalue
|
左辺値参照に一時オブジェクトは渡せないのでコンパイルエラー
|
const T& param |
「コピーしない。const参照する。参照先は変更しない、という意思。」 (int)lvalue |
int |
const int& param
|
(const int)lvalue |
int |
const int& param
|
(int&)lvalue |
int |
const int& param
|
(const int&)lvalue |
int |
const const int& param
|
(int*)lvalue |
int* |
int* const& param
|
(const int*)lvalue |
const int* |
const int* const& param
|
(int)rvalue
|
int |
const int& param ※const参照が一時オブジェクトをバインドする奴
|
T&& param |
「コピーしない。あるがまま参照する。受け取ったパラメータは変更するかも。一時オブジェクトは捕まえる。」 (int)lvalue |
int& |
int& param
|
(const int)lvalue |
const int& |
const int& param
|
(int&)lvalue |
int& |
int& param
|
(const int&)lvalue |
const int& |
const int& param
|
(int*)lvalue |
int*& |
int*& param
|
(const int*)lvalue |
const int*& |
const int*& param
|
(int)rvalue
|
const int |
int&& param ※右辺値参照を捕まえるやつ。move。
|
|
T&&
- ユニバーサル参照あるいはフォワード参照 - はそれ単体で別途説明が必要なモノではありますが、全体としては納得の推論です。
いままでコンパイラに怒られなければいっかとフィーリングで実装していましたが、問題なさそうで安心しました。
auto型推論
テンプレート型推論の T
を auto
に、ParamType
をauto
の修飾に変更すれば、auto
の型推論表のできあがりです。
ParamType | expr | auto推論結果 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
auto param 「全部コピーして所有する」 |
= (int)lvalue | int param | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
= (const int)lvalue | int param | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
= (int&)lvalue | int param | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
= (const int&)lvalue | int param | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
= (int*)lvalue | int* param | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
= (const int*)lvalue | const int* param | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
= (int)rvalue | int ※コピーコンストラクタが呼ばれるかどうかは時と場合による auto* param |
「ポインタしか受け取らない」 (int)lvalue |
ポインタでないのでコンパイルエラー
|
= (const int)lvalue |
ポインタでないのでコンパイルエラー
|
= (int)lvalue |
ポインタでないのでコンパイルエラー
|
= (const int&)lvalue |
ポインタでないのでコンパイルエラー
|
= (int*)lvalue |
int* param
|
= (const int*)lvalue |
const int* param
|
= (int)rvalue
|
ポインタでないのでコンパイルエラー
|
const auto* param |
「ポインタしか受け取らない。受け取ったポインタが指す先は変更しない。」 = (int)lvalue |
ポインタでないのでコンパイルエラー
|
= (const int)lvalue |
ポインタでないのでコンパイルエラー
|
= (int)lvalue |
ポインタでないのでコンパイルエラー
|
= (const int&)lvalue |
ポインタでないのでコンパイルエラー
|
= (int*)lvalue |
const int* param
|
= (const int*)lvalue |
const int* param
|
= (int)rvalue |
ポインタでないのでコンパイルエラー
|
auto& param |
「コピーしない。あるがまま参照する。受け取ったパラメータは変更するかも。」 = (int)lvalue |
int& param
|
= (const int)lvalue |
const int& param
|
= (int&)lvalue |
int& param
|
= (const int&)lvalue |
const int& param
|
= (int*)lvalue |
int*& param
|
= (const int*)lvalue |
const int*& param
|
= (int)rvalue
|
左辺値参照に一時オブジェクトは渡せないのでコンパイルエラー
|
const auto& param |
「コピーしない。const参照する。参照先は変更しない、という意思。」 = (int)lvalue |
const int& param
|
= (const int)lvalue |
const int& param
|
= (int&)lvalue |
const int& param
|
= (const int&)lvalue |
const const int& param
|
= (int*)lvalue |
int* const& param
|
= (const int*)lvalue |
const int* const& param
|
= (int)rvalue
|
const int& param ※const参照が一時オブジェクトをバインドする奴
|
auto&& param |
「コピーしない。あるがまま参照する。受け取ったパラメータは変更するかも。一時オブジェクトは捕まえる。」 = (int)lvalue |
int& param
|
= (const int)lvalue |
const int& param
|
= (int&)lvalue |
int& param
|
= (const int&)lvalue |
const int& param
|
= (int*)lvalue |
int*& param
|
= (const int*)lvalue |
const int*& param
|
= (int)rvalue
|
int&& param ※右辺値参照を捕まえるやつ。move。
|
|
おわりに
一通りまとめましたが、赤文字部分の詳細とか、配列を実引数に入れた場合とか、関数ポインタを入れた場合とか、波括弧 (初期化の統一記法) での初期化など、語るべき点が残っています。また、「仕様はわかったから実際のユースケースくれ」というむきもあるでしょう。
そのあたりついては、おいおい追記したいと思っていますが、勉強しながらになりますのでしばしお待ちください。
本記事に興味がある方・もっとkwskという方は、Effective Modern C++を参照頂ければと思います。というか、そこに全部書いてあります。