template <typename T>
void func(ParamType Type);
4つ注意点がある
template <typename T>
void func(ParamType Type);
// 参照
template <typename T>
void func(T& Type);
int x = 27; func(x); // T: int, ParamType: int&
const int cx = x; func(cx); // T: const int, ParamType const int&
const int& rx = cx; func(rx); // T: const int, ParamType const int&
// ポインタ
template <typename T>
void func(T* Type);
int x = 27; func(&x); // T: int, ParamType: int*
const int* p = x; func(p); // T: const int, ParamType: const int*
とても直観的。
// ユニヴァーサル参照
template <typename T>
void func(T&& Type);
// 左辺値
int x = 27; func(x); // T: int, ParamType: int&
const int cx = x; func(cx); // T: const int, ParamType: const int&
const int& rx = x; func(rx); // T: const int&, ParamType: const int&
// 右辺値
func(27); // T: int, ParamType: int&&
特殊なのは、PamamType
が左辺値の場合。
// 値渡し
template <typename T>
void func(T Type);
int x = 27; func(x); // T and ParamType: int
const int cx = x; func(cx); // T and ParamType: int
const int& rx = x; func(rx); // T and ParamType: int
const* char const p = "aaa"; func(p); // T and ParamType: const char* (pのconst性が無視)
値渡しの場合、仮引数は実引数のコピーなので別物なので、参照性/const/volatileは無視される
// 値渡し
template <typename T>
void func(T Type);
// 配列型, ポインタ型
const char name[] = "Sato Jonathan";
func(name); // T and ParamType: const char*
// 関数型, ポインタ型
void somefunc(int, double); // 型はvoid(int, double)
func(somefunc); // T and ParamType: void(*)(int, double)
// 参照で解決!
template <typename T>
void func(T& Type);
func(name); // T and ParamType: const char[14]
func(somefunc); // T and ParamType: void(&)(int, double)
配列型や関数型は参照渡しによってポインタ型に変換されない
int x = 0;
auto&& uref = x; // uref: int&
1点を除いて、templateと挙動は同じ。
int x = 0;
auto&& uref = x; // uref: int&
autoの場合
auto x1 = 27; // int
auto x2(27); // int
auto x3{27}; // int
auto x4 = {27}; // initializer_list<int>
templateの場合
template<typename T>
func1(T Type);
template<typename T>
func2(initializer<T> Type);
func1({1, 3, 5}); // error
func2({1, 3, 5}); // OK
templateはinitializer_listを推論できない
// 関数
auto func() // error
{
return {1, 3, 5};
}
// ラムダ式
auto l2 = [&v](const auto& newVal){ v = newVal;};
l2({1, 3, 5}); // error
関数の戻り値, 仮引数にauto
を使うとinitializer_list
を推論できない == template
と挙動が同じ
int& i; // decltype(i): int&
decltypeの主要用途と、1つの注意点。
int& i; // decltype(i): int&
autoの推論の規則をdecltypeの規則にする
// auto の規則により参照が外れる(戻り値の型: int)
auto authAndAccess(std::vector<int>& v, std::size_t i)
{
return v[i];
}
// decltype の規則によって推論する(int&)
decltype(auto) authAndAccess(std::vector<int>& v, std::size_t i) // 戻り値の型はdecltype(v[i])
{
return v[i];
}
名前でなく, (複雑な)左辺値式を仮引数とした場合、参照型となる
// 名前の場合, 戻り値の型はint
decltype(auto) func1()
{
int x = 0;
return x; // decltype(x): int
}
// 複雑な左辺値式の場合, 戻り値の型はint&
decltype(auto) func2()
{
int x = 0;
return (x); // decltype((x)): int&
}
auto
は少し複雑な推論規則がデメリットになりうるが、それでもauto
宣言を使うべき
auto
は少し複雑な推論規則がデメリットになりうるが、それでもauto
宣言を使うべき- メリット
- 初期化の強制
int x; // 初期化しなくてもコンパイルできる auto x1; // error! auto x2 = 0; // OK!
- 型の不一致を防げる
std::unordered_map<std::string, int> m; // 型の不一致! 暗黙の型変換が発生してしまう。 for(const std::pair<std::string, int>& e : m) // std::unordered_mapのキーがconstになる仕様を知らなかった… { ... } // OK! for(const auto& e : m) { ... }
- リファクタリングを容易にする
auto func() { // 変更前 //int x = 0; // 変更後 long x = 0; return x; } auto gx = func(); // ここを書き換える必要がない
- デメリット
-
推論規則を理解しないといけない
-
ユーザーに意識させないプロクシクラス
プロクシクラスは効率を上げるために作られたが、変数として扱うことを想定されていない
// 例 std::vector<bool> func1(); // b1はstd::vector<bool>::reference(ワードへのポインタとオフセットを保持したオブジェクト) auto b1 = func()[n]; // b1のポインタが指すオブジェクトは一時オブジェクトなのでこの段階では既に破棄されており未定義動作 bool _b1 = b1; bool b2 = func()[n]; // 設計者が想定した使い方。暗黙の型変換によりプロクシクラスをユーザーに意識させない bool b3 = static_cast<bool>(func()[n]); // OK! 未定義動作を回避できるし、vb[n]がbool型ではないことを明示できる
-
Effective Modern C++ ―C++11/14プログラムを進化させる42項目 Scott Meyers著、千住 治郎訳