Skip to content

Bob8percent/cpp_rule

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 

Repository files navigation

cpp_rule

templateの型推論

template <typename T>
void func(ParamType Type);

4つ注意点がある

ParamTypeが参照, ポインタの場合(ユニヴァーサル参照ではない)

// 参照
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*

とても直観的。

ParamTypeがユニヴァーサル参照の場合

// ユニヴァーサル参照
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が左辺値の場合。

ParamTypeが値渡しの場合

// 値渡し
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)

配列型や関数型は参照渡しによってポインタ型に変換されない

autoの型推論

int x = 0;
auto&& uref = x;	// uref: int&

1点を除いて、templateと挙動は同じ。

intializer_listの挙動

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と挙動が同じ

decltypeの型推論

int& i;	// decltype(i): int&

decltypeの主要用途と、1つの注意点。

主要用途

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宣言を使うべき

  • メリット
    • 初期化の強制
     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();	// ここを書き換える必要がない
  • デメリット
    • 推論規則を理解しないといけない

      • autoの型推論

      • templateの型推論

      • ユーザーに意識させないプロクシクラス

        プロクシクラスは効率を上げるために作られたが、変数として扱うことを想定されていない

       //
       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著、千住 治郎訳

About

C++覚書

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published