Effective Modern C++勉強会 #2 Item 9, 10
内田公太(@uchan_nos)
サイボウズ株式会社
2015/02/25
Item 9, 10
• Item 9: Prefer alias declarations to typedefs.
• Item 10: Prefer scoped enums to unscoped enums.
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• typedef と using は同じような見た目
• using の技術的優位性はまだ見えない
typedefstd::unique_ptr<std::unordered_map<std::string, std::string>>
UPtrMapSS;
using UPtrMapSS =std::unique_ptr<std::unordered_map<std::string, std::string>>;
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• ポインタが関係する例だと、using が解読しやすい
• が、明らかに using に優位性があるとは言えない
• テンプレートに関係してくると優位性がはっきりする
typedef void (*FP)(int, const std::string&);
using FP = void (*)(int, const std::string&);
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• using はテンプレート化が可能(alias templates と呼ばれる機能)
• typedef はテンプレート化できないので、テンプレート化した struct 中に記述する→ その型を使うときにもっと深刻になる
template<typename T>using MyAllocList = std::list<T, MyAlloc<T>>;
template<typename T>struct MyAllocList {typedef std::list<T, MyAlloc<T>> type;
};
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• typedef の MyAllocList<T>::type は型名なのかどうか、コンパイラには分からない“dependent type”
• using の MyAllocList<T> は型名だと分かる“non-dependent type”
template<typename T>class Widget {MyAllocList<T> list;
};
template<typename T>class Widget {typename MyAllocList<T>::type list;
};
typedefの場合
using の場合
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• MyAllocList<Wine>::type は型名ではない!
• typename MyAllocList<T>::type と書く所以
class Wine { … };
template<>class MyAllocList<Wine> {enum class WineType{ White, Red, Rose };
WineType type;…
};
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• <type_traits> で提供される型変換テンプレートはほとんどの場合テンプレートパラメタ T に対して使う→ typename を付け足す必要
• C++14 からは using 版の型変換が定義されている→ typename はもちろん不要、 ::type も不要
std::remove_const<T>::typestd::remove_reference<T>::typestd::add_lvalue_reference<T>::type
std::remove_const_t<T>std::remove_reference_t<T>std::add_lvalue_reference_t<T>
C++11 の場合
C++14 から
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• C++14 が使えないとしても C++11 が使えさえすればOK
• 朝飯前の作業だし、さくっと定義しちゃお!
template <class T>using remote_const_t = typename remove_const<T>::type;
template <class T>using remote_reference_t =typename remove_reference<T>::type;
template <class T>using add_lvalue_reference_t =typename add_lvalue_reference<T>::type;
Item 9: Prefer alias declarations to typedefs.typedefよりも別名型宣言を使おう
• typedef はテンプレート化をサポートしないが別名型宣言はサポートする
• 別名型テンプレートは ::type と typename が不要
• C++14 は type traits の全ての型変換に対して別名型テンプレートを用意している
覚えておくべきこと
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• enum class は外部に識別子が漏れない• enum class を使う第1の理由
enum Color { black, white, red };
auto white = false; // error!
enum class Color { black, white, red };
auto white = false; // fine
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• 名前空間が汚れないだけでも enum class を使う動機として十分
• 加えて enum class は強く型付けされる• enum class を使う第2の理由
enum class Color { black, white, red };
Color c = white; // error!
Color c = Color::white; // fine
auto c = Color::white; // fine (Item 5)
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• 古い enum の列挙子は暗黙に整数型に変換できる
• すなわち、暗黙に浮動小数点数型にも変換できる
• class を付けるだけで、暗黙の型変換が無くなり、意味論の破たんを防止できる
enum Color { black, white, red };
std::vector<std::size_t> primeFactors(std::size_t x);
Color c = red;…if (c < 14.5) {auto factors = primeFactors(c);…
}
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• どうしても Color 型から他の型に変換したいなら、型システムを歪めて無茶な希望に合わせるために、あなたがいつもやっていることをやりなさい――キャストを使うのです。
if (static_cast<double>(c) < 14.5) {auto factors =primeFactors(static_cast<std::size_t>(c));
…}
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• enum class は前方宣言が出来る• enum class を使う第3の理由、に見えるかもしれない
• 実は C++11 では古い enum も前方宣言できる• enum Color: std::uint8_t;
enum Color; // error!
enum class Color; // fine
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• すべての enum は “underlying type” を持つ
• 古い enum では、コンパイラが自動で決める• 上記の例では、0xFFFFFFFFが表せる最小の整数型
• または、最速な整数型になる、かもしれない
• enum class では、デフォルトが int と決まっている
enum Status { good = 0,failed = 1,incomplete = 100,corrupt = 200,indeterminate = 0xFFFFFFFF
};
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• といっても、古い enum が役立つ場面はある
• std::tuple のフィールドを参照するとき、フィールド 1 が email に対応することを覚えてなんかいられない
• そこで古い enum が役立つ
using UserInfo = std::tuple<std::string, // namestd::string, // emailstd::size_t> // reputation
UserInfo uInfo;…auto val = std::get<1>(uInfo);
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• これはひどい
enum UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;…auto val = std::get<uiEmail>(uInfo);
enum class UserInfoFields { uiName, uiEmail, uiReputation };
UserInfo uInfo;…auto val =std::get<static_cast<std::size_t>(UserInforFields::uiEmail)>(uInfo);
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• 古い enum より依然としてタイプ量は多い
• 名前空間の汚染と意図しない変換は避けられる
• 2400bpsモデムの時代の最先端 enum 技術の落とし穴を避けるのに、多少のタイピング増加は妥当な対価である
template<typename E>constexpr autotoUType(E enumerator) noexcept
{return static_cast<std::underlying_type_t<E>>(enumerator);
}
auto val =std::get<toUType(UserInfoFields::uiEmail)>(uInfo);
Item 10: Prefer scoped enums to unscoped enums.古いenumよりスコープ付きenumを使おう
• C++98 時代の enum は unscoped enum と呼ばれる
• scoped enum の列挙子はその enum の中だけで見え、他の型への変換は必ずキャストが必要
• scoped enum / unscoped enum ともに underlying type を指定できる• scoped enum のデフォルトは int
• unscoped enum はデフォルトを持たない
• scoped enum は前方宣言できる。 unscoped enum はunderlying type を指定したときだけ前方宣言できる。
覚えておくべきこと