search
LoginSignup
4

More than 5 years have passed since last update.

posted at

updated at

autoの推論まとめ、あるいはautoは忖度などしない件

はじめに

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

以上のメタな文字ParamTypeexprをいろいろ変えてみて、TParamTypeが結局何になるのかの一覧表を示します。

右辺値は目立つよう(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型推論

テンプレート型推論の Tautoに、ParamTypeautoの修飾に変更すれば、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++を参照頂ければと思います。というか、そこに全部書いてあります。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
4