サバです。最近EffectiveModernC++をやっと買えたと思ったら、ついでに期末試験が迫ってきてしまったので、現実逃避しながらその内容について書いてきたいと思います。(本の内容を自分解釈で出力しとけば忘れたときに楽かなぁ…とも思ったので!)
この本にとっかかりやすくなってもらえるとうれしいなぁ
<注意>
ここの内容は、サバが適当な自己解釈を重ねた結果、攪拌され白濁してしまった知識の泉から、沈殿物を素手で持ち上げようとした結果です。保証はしかねます。ご了承ください。
題名にEffectiveModernC++が入っていないのは更新が途中で止まる自信があるからです。
また、もしよろしければ、間違い、気になったところ、分かりづらいところなどを指摘してもらえると、サバがピチピチ跳ねて喜びます。喜びすぎてプログラミング言語C++第四版をあなたに投げつけるかもしれません。
(ぜひ教えてください。よろしくお願いします。 < ( _ _ ) > 何でもするとは言いませんから…)
1章 型推論
項目1 テンプレートの型推論を理解する
結論:大体期待通り!
C++の深淵は闇の中だとしても、templateは晴れた日のあなたの部屋くらい明るい。
C++なんて、第一に安全、第二に効率を考えて、Cから派生したってことだけ分かっていれば、もう何も怖くない!!(そう願いたい…)
まさにtemplateはそんな感じ。(希望的観測)
template<typename T> と宣言したとき、Tの使い方は参照について考えると3つあります。
1. void f (T arg) { } : arg は値渡し
- const , volatileは完全になかったことにされる
2. void f (T& arg) { } : arg は参照渡し
3. void f (T&& arg) { } : arg はユニバーサル参照渡し
- ユニバーサル参照 : 左辺値参照・右辺値参照のどちらもイける両刀使い
上記の f を f (value) と呼び出したとき、 T と arg の
推論される型を表に入れるとこうなります。大体…期待通りです。(volatileもconstと同じ)
T + & |
T |
T& |
T&& |
T |
arg |
T |
arg |
T |
arg |
int value; |
int |
int |
int |
int& |
int& |
int& |
int& value; |
int |
int |
int |
int& |
int& |
int& |
int&& value; |
int |
int |
int |
int& |
int& |
int& |
const int value; |
int |
int |
const int |
const int& |
const int& |
const int& |
const int& value; |
int |
int |
const int |
const int& |
const int& |
const int& |
const int&& value; |
int |
int |
const int |
const int& |
const int& |
const int& |
f ( 1 ) // 何か右辺値 |
int |
int |
error |
error |
int |
int&& |
- Tの時、constも参照も取れるのは、値渡しでできた複製(仮引数)がどうなろうとコピー元(実引数)には関係ないからです。
- エラーが出るのは、右辺値を書き換えても意味がなく、リテラルの参照が存在したらわけわかめになるからだとおもいます。(適当)
- ユニバーサル参照はいろいろ変態なので覚えるのが大変そうです…
・template? 知らない子ですね
君は、関数に値を渡すだけの退屈な日々を過ごす中で、
型も渡せたらいいのにと思ったことはあるかい?
そういうことだよ。()
・深淵を覗きに行くために…
(闇のコードを読むことに興味がある方は知っているべき?)
ついでにポインタ、配列、関数も入れてみましょう
T + & + α |
T |
T& |
T&& |
T |
arg |
T |
arg |
T |
arg |
const int * const value; |
const int * |
const int * |
const int * const |
const int * const & |
const int * const |
const int * const & |
int value[4]; |
int * |
int * |
int [4] |
int (&)[4] |
int [4] |
int (&)[4] |
void value( int ); |
void (*)(int) |
void (*)(int) |
void (int) |
void (&)(int) |
void (int) |
void (&)(int) |
- int value[4] は長さ4の int 型配列、void value( int ) は int 型引数一つを受け取る void 関数です。
- 注目するは青文字です。値渡しでは配列や関数をポインタとして推論しています。これはC言語由来の動作で、配列や関数がポインタに成り下がる(本当はポインタじゃないけどポインタのふりをする)という動作をするためです。
- 参照渡しはポインタのふりをする必要がないため、配列・ポインタの参照型として推論されます。
Tにconstつけるとこうなります。
(VS2017でこうなるを確認できたけど理由がよくわかってなかったり…
T + const + & |
const T |
const T& |
const T&& |
T |
arg |
T |
arg |
T |
arg |
int value; |
int |
const int |
int |
const int& |
error |
error |
int& value; |
int |
const int |
int |
const int& |
error |
error |
int&& value; |
int |
const int |
int |
const int& |
error |
error |
const int value; |
int |
const int |
int |
const int& |
error |
error |
const int& value; |
int |
const int |
int |
const int& |
error |
error |
const int&& value; |
int |
const int |
int |
const int& |
error |
error |
f ( 1 ) // 何か右辺値 |
int |
const int |
int |
const int& |
int |
const int && |
- const T&& は要らない子だと思ってたけど右辺値だけ受け取りたいときに使える…?
- 右辺値と右辺値参照が頭の中でこんがらがってあぁぁぁぁ
- 迷ったら const 参照使ってれば一番よさそうですね
ポインタになるとこうなります
※ const が長くて表にはまらなかったのでcとしました
T + const + * |
T * |
T * const |
const T * |
const T * const
|
T |
arg |
T |
arg |
T |
arg |
T |
arg |
int * value; |
int |
int* |
int |
int* c |
int |
c int* |
int |
c int* c |
int * const value; |
int |
int* |
int |
int* c |
int |
c int* |
int |
c int* c |
const int * value; |
c int |
c int* |
c int |
c int* c |
int |
c int* |
int |
c int* c |
const int * cosnt value; |
c int |
c int* |
c int |
c int* c |
int |
c int* |
int |
c int* c |
- const とポインタの関係が完璧に分かっているconst教信者の方なら、const T * とか T * const とかしたときはどうなるか、すぐに予想できましたよね?(震え声)
ね? templateって、素直ないい子…だよ…ね?
項目2 autoの型推論を理解する
結論:templateと同じ!以上!解散!
とは言えないみたいです…
1か所だけ異なって、auto value = {1, 2, 3, 4, 5, 12}; と{}で書いたときのみ、
value は std::initializer_list<T>という型に推論されます。
左右は全く同じ意味 |
auto i = 1; |
int i = 1; |
const auto ci = i; |
const int ci = i; |
const auto d = 1.0; |
const double d = 1.0; |
auto d2 = d; |
double d2 = d; |
auto& ir = i; |
int& ir = i; |
const auto& cir = ir |
const int& cir = ir; |
auto& cir2 = cir; |
const int& cir2 = cir; |
auto hentai = {1}; |
std::initializer_list<int> hentai = {1}; |
auto hentai = {1, 1.0}; |
error (intなのかdoubleなのか、少しでも曖昧なため) |
・auto? 知らない子ですね
C++にはイテレータというtemplateクラス版ポインタがあるんだけど、
そいつが指す変数の「型」を取得するためにこんなコードは書きたくないよね?
template<typename Iterator>
void PrintValue(Iterator iterator)
{
std::iterator_traits<iterator>::value_type value = *iterator;
std::cout << value;
}
大丈夫。こんなの覚えなくていい。そう、autoならね。
template<typename Iterator>
void PrintValue(Iterator iterator)
{
auto value = *iterator;
std::cout << value;
}
・深淵を覗きに行くために…
ラムダ式の引数や関数の戻り値など、関数の定義等にもautoを使えますが、
これはtemplateの型推論になるらしいです。
auto lambda = [](auto arg){ };
lambda({1, 3, 4}); // error std::initializer_list<int>と推論できない
auto plsGiveMe_initializer_list()
{
return {1, 2, 3}; // error std::initializer_list<int>と推論できない
}
また、std::initialzier_list<>の型推論には以下のようにC++のバージョンに左右される面があります。(デフォルトのVisualStudio2017は、C++17以降の方の動作をします。)
ソースコード |
推論結果 |
C++14以前 |
C++17以降 |
auto value = {0, 1, 2}; |
std::initializer_list<int> value = {0, 1, 2}; |
std::initializer_list<int> value= {0, 1, 2}; |
auto value = {0}; |
std::initializer_list<int> value = {0}; |
std::initializer_list<int> value = {0}; |
auto value{0, 1, 2}; |
std::initializer_list<int> value = {0, 1, 2}; |
error |
auto value{0}; |
std::initializer_list<int> value = {0}; |
int value = 0; |
項目3 decltypeを理解する
結論 : そのまんま型が返ってくる!(大体そのまんま)
decltypeにも例外的な動作が一つあります。
単純に名前ではない左辺値式をあたえたときは
参照がもれなく付いてくるというものです。
int n; // 左辺値式 n
decltype(n) → int // 単純に名前だけの左辺値式
decltype((n)) → int& // この場合は()が付いたため複雑な左辺値式と判定されるらしい…
項目2の変数と次の関数を使います。 void kansu(int, double){}
左右は全く同じ意味 |
decltype(i) |
int |
decltype(cir2) |
const int& |
decltype(hentai) |
std::initializer_list<int> |
decltype(kansu(0, 0)) |
void |
decltype(kansu) |
void ( int, double ) |
decltype((i)) |
int& // 唯一の例外 |
decltype((i + 1 + 0.1)) |
double // 右辺値式は例外に含まれない |
・decltype? 知らない子ですね
知らなくても大丈夫。ゴリゴリtemplate書くようになるまで必要になることはないから。
それに、必要になったらいやでも覚えなきゃいけないから。(未来の自分がそう言ってる気がする)
・深淵を覗きに行くために…
decltypeは、autoの用に変数の型・関数の戻り値を推論するのに使えます。書式は以下の通りです。
ただし推論の方法はdecltypeのものであり、auto や template とは異なります。
左右は全く同じ意味 |
decltype(auto) value = 0;
decltype(auto) func()
{
return 0;
} |
int value = 0; // 0 はint型
int func() // 0 はint型
{
return 0;
} |
const int n = 0;
decltype(auto) value = n;
decltype(auto) func()
{
return n;
} |
cont int n = 0;
const int value = n; //templateやautoはintと推論
const int func() //templateやautoはintと推論
{
return n;
} |
int n = 0;
decltype(auto) value = (n); // 複雑な左辺値式
decltype(auto) func()
{
return (n); // 複雑な左辺値式
}
|
int n = 0;
int& value = n; // 参照が付きます
int& ILoveDecltype() // 参照が付きます
{
return n;
} |
decltype(auto) value = {1, 2, 3};
decltype(auto) func()
{
return {1, 2, 3}; // std::initializer_list<int> ?
} |
error
(autoを使いますが、型の推論方法はdecltypeのものです。そのためstd::initializer_list<int>とは推論できません。) |
項目4 推論された型を確認する
結論 : IDEに頼ろう!
IDE(VisualStudioとかとかとか)で気になるトークンにマウスオーバー。
これが一番(個人比)
Boost.TypeIndexというBoostライブラリも紹介されていました。
でも変な型が出る可能性は十分あるという…
そのほかにはコンパイルエラーで確認する方法、場合によっては見づらい・不正確な型名を表示する可能性がある方法なども紹介されていました。
< 前の項目へ 次の項目へ >