なんだかんだと4回目を迎えたC++17。
ほぼ
https://cpprefjp.github.io/lang/cpp17.html
をなぞっているだけですがとりあえずまだやっていきます。
今回は制御構造に関するC++17
・if文とswitch文の条件式と初期化を分離
これだけだとわかったようなわからないような感じですが
初期化に関することで例えば従来だとfor文で
1 2 3 4 5 6 7 8 9 |
#include <iostream> int main() { for(int i = 0; i < 10; ++i){ std::cout << i << std::endl; } // std::cout << i << std::endl; //->エラーになる } |
こう書くとforで定義されたiはfor文内でのみ有効でそれ以外でiを使おうとするエラーです。
これをC++17ではif文でも初期化式として
if (初期化式; 条件式)
てな感じで書けるので、
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> #include <set> int main() { std::set<int> st{3, 1, 4}; if(auto r = st.insert(3); !r.second){ std::cout << "すでに登録されています" << std::endl; }else{ std::cout << "正常に登録されました" << std::endl; } } |
上記のようになります。
setは重複を許さないので重複しているかどうかのチェックです。C++17じゃない場合は
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <set> int main() { std::set<int> st{3, 1, 4}; auto r = st.insert(3) if(r.second){ }else{ } } |
でしょうか。ただ今回の例であればif(st.insert(3))で済みますが。
これがelseとかで正常に登録されたstを使って何かする、とかであればこうもいかないので便利になったでしょう。
switchに関してもifの条件式がswitchに代わるぐらいです。
for文でできるんだからif,switchであってもおかしくないのでよかったのではないかと思います。
・[[fallthrough]]属性
前回やったので割愛
・if constexpr 文
コンパイル時に条件分岐する文。
これもわかったようなわからないような。ちなみにD言語でもstatic ifとかでコンパイル時評価されるらしいがD言語は知らない..
テンプレートのインスタンス化時(コンパイル時)に、どの実装を生成するかを簡潔に書けるようになるのだが
このあたりテンプレートが絡んでくるのでそもそもテンプレートをよく知らないのであれば
なんのこっちゃということになる。
このあたりははっきりこのあたりを読んでようやく理解
https://qiita.com/saka1_p/items/e8c4dfdbfa88449190c5
https://cpplover.blogspot.jp/2017/05/constexpr-if.html
https://github.com/EzoeRyou/cpp17book/blob/master/028-cpp17-core-constexpr-if.md
例で挙げられているコンパイル時に条件分岐させたいものとすれば型、intやfloat,doubleなどコンパイル時にわかっているのであれば
そこで決めてしまう。もし意図しない型であればコンパイルエラーとなって動いているときに
変な動きにならないのでよく知らずに使っても事前に防げるということだ。
1 2 3 4 5 6 7 8 9 10 11 12 |
template<typename T> void f() { if constexpr (std::is_same_v<T, int>) { // Tがintのときエラーになると思いきや常にエラーになってしまう static_assert(false) ; }else if constexpr ( std::is_integral{} ) { // 通常処理 } } |
参考ページそのままのコードとなってしまうが上記のように型がintの場合は意図しないとしてエラーにしたいが
コメントの通り常にエラーとなってしまう。
(static_assertはコンパイル時アサートをチェックするための関数)
理由としてはテンプレート仮引数Tに依存していないため、テンプレートの宣言時に検証され、エラーとなる。
逆に言えば仮引数Tに依存しているようになればOKということである。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
#include <type_traits> template <typename T> constexpr bool false_v = false; template < typename T > void f() { if constexpr ( std::is_same<T, int>{} ) { // Tがintのときのみ発動してほしい // 実際は常に発動する static_assert( false_v<T> ) ; } } |
言われてみればなるほどねーとなるのだが自分の頭ではなかなかこういった解法が思いつかないので
勉強になる。
・範囲 for ループの制限緩和
begin と end の型が同じでなければならなかった。 C++17 でこの制限が緩和された。
そもそもC++11でfor文が
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <vector> int main() { std::vector<int> v{0,2,4}; for (std::vector<int>::const_iterator it = v.begin(); it != v.end(); ++it) { std::cout << *it << std::endl; } } |
を
1 2 3 4 5 6 7 8 9 10 11 |
#include <iostream> #include <vector> int main() { std::vector<int> v{0,2,4}; for (const auto& e : v) { std::cout << e << std::endl; } } |
とすっきりかけるようになった。
範囲for文は
for ( for-range-declaration : for-range-initializer ) statement
となるのだが
1 2 3 4 5 6 7 8 9 |
{ auto && __range = for-range-initializer; for ( auto __begin = begin-expr, __end = end-expr; // __begin と __end は同じ型でなければならない __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } } |
このような展開がされるので__beginと__endが同じ型でなければいけなかった。が、
1 2 3 4 5 6 7 8 9 |
{ auto && __range = for-range-initializer; auto __begin = begin-expr; auto __end = end-expr; // __begin と __end は異なる型でもよい for ( ; __begin != __end; ++__begin ) { for-range-declaration = *__begin; statement } } |
C++17では上記のような形で展開されるようになったので__beginと__endが異なっていてもよくなった。
こちらの例で載っているがbeginとendがそれぞれ異なる方になっているがOKとなっている
https://cpprefjp.github.io/lang/cpp17/generalizing_the_range-based_for_loop.html
今まで同じ型だけだったことで苦労したことはなかったと思うがそれはただ単にそんなことを思いつきも
しなかっただけなので使えるとなるといろいろとケースがでてくるだろう..たぶん。
今回テンプレート関連など正直そのあたりをちゃんとやらないといけない感じだったなあ..
まあC++17だけでなく今後何かしらC++やる場合はそのあたりをちゃんとできればいいが果たしてその日は来るのか。
過去分
C++17を知るその1
C++17を知るその2
C++17を知るその3
参考URL
C++日本語リファレンス
if constexprを使うとき、特定条件時にコンパイルを失敗させる
constexpr ifの落とし穴
constexpr if文 : コンパイル時条件分岐