【一人 C++20 Advent Calendar 2019】状態を持たないラムダ式がデフォルト構築可能、代入可能となった【24日目】
一人 C++20 Advent Calendar 2019 24日目の記事になります。
状態を持たないラムダ式がデフォルト構築可能、代入可能となった
状態を持たないラムダ式がデフォルト構築可能、代入可能となりました。
次のようにラムダ式の型からオブジェクトを定義することができます。
#include <iostream> #include <map> int main(){ auto hello_world = [] { std::cout << "hello, world" << std::endl; }; // lambda の型から新しいオブジェクトを生成できる decltype(hello_world) hello_world2{}; hello_world2(); // 状態を持っている(キャプチャしている)場合は生成できない auto hello_world3 = [&] { hello_world(); }; // NG // decltype(hello_world3) hello_world4{}; return 0; } /* output: hello, world */
なるほど? これだけだと何が嬉しいんだと思いますよね。
こういう時に便利
std::map
は第三テンプレート引数に比較型を渡すことができます。
これは通常は関数オブジェクト型を定義し、 operator ()
などで処理を記述する必要がありました。
しかし、ラムダ式がデフォルト構築可能になったことで次のようにテンプレート引数に対して直接処理を記述できるようになります。
#include <iostream> #include <map> int main(){ // std::map は第三テンプレート引数に比較型を渡すことができる // 本来は関数オブジェクトクラスを定義してテンプレート引数に渡す必要があった // これを delctype を経由して直接ラムダ式で処理を記述できるようになった auto data = std::map< int, char, decltype([](auto a, auto b) { return a > b; }) > { { 1, 'A' }, { 11, 'J' }, { 12, 'Q' }, { 13, 'K' } }; for(auto&& [key, value] : data){ std::cout << key << ":" << value << std::endl; } return 0; } /* output: 13:K 12:Q 11:J 1:A */
テンプレート引数に直接式を記述したい!!!!って C++11 の頃からずーーーーーっとやりたいと思っていてそんなことをするライブラリも自前でつくったりしていました。
C++20 だと delctype
+ ラムダ式で直接テンプレート引数に処理を記述できるようになるのでかなりメタプログラミングが捗りそうですね…これだよこれを待っていたんだよ!!!
対応コンパイラ
- GCC 9
- Clang 8.0
参照
【一人 C++20 Advent Calendar 2019】C++ にモジュールがやってくる!【23日目】
一人 C++20 Advent Calendar 2019 23日目の記事になります。
C++ にモジュールがやってくる!
ついに C++ にモジュールがやってきます!
モジュールとはヘッダーファイル・インクルードに置き換わる『外部ファイル・実装を取り込む新しい仕組み』になります。
よくわからんので実際に簡単なコードを書いてみます。
今までのヘッダーファイル・インクルード
これまではヘッダーファイルで宣言や定義を行い、それを include
することで外部ファイルを取り込んでいました。
// math.h #ifndef MATH_H #define MATH_H template<typename T, typename U> auto plus(T a, U b){ return a + b; } #endif /* MATH */
// main.cpp #include "./math.h" #include <iostream> int main(){ std::cout << plus(1, 2) << std::endl; std::cout << plus(3.14, 3.14) << std::endl; return 0; } /* output: 3 6.28 */
モジュールを使った実装
モジュールを使用すると以下のように置き換えることができます。
// math.cppm // モジュールの宣言 export module math; // エクスポートの宣言 export template<typename T, typename U> auto plus(T a, U b){ return a + b; }
// main.cpp // インポートの宣言 // エクスポート宣言した関数が使用できるようになる import math; #include <iostream> int main(){ std::cout << plus(1, 2) << std::endl; std::cout << plus(3.14, 3.14) << std::endl; return 0; } /* output: 3 6.28 */
基本的には
- エクスポートで実装
- インポートで取り込み
っていう使い方ですかね。
見てわかるように今までの書き方とは全然違います。
わかりやすいところだとインクルードガードも必要なくなり、 include
のようなファイルパス指定もなくなります。
ビルド方法
インクルードする場合はファイルを指定して展開するだけなので簡単な構成の場合のビルドはそこまで難しくなかったのですが、モジュールを使った場合は少し複雑になります。
clang 8
で試してみたんですが以下のようにビルドします。
# clang 8 での動作確認 $ clang++- --version clang version 8.0.1-svn369350-1~exp1~20190820121219.79 (branches/release_80) Target: x86_64-pc-linux-gnu Thread model: posix InstalledDir: /usr/bin
# モジュールファイルを指定してプリコンパイル済みモジュールを生成する $ clang++ -std=c++2a -fmodules-ts --precompile math.cppm -o math.pcm
# プリコンパイル済みモジュールからバイナリファイル(.o) を生成 $ clang++ -std=c++2a -fmodules-ts -c math.pcm -o math
# 実行ファイルを生成 $ clang++ -std=c++2a -fmodules-ts -fprebuilt-module-path=. math.o main.cpp -o math
モジュールを取り込んで実行バイナリを生成するまでに3つのステップあります。
1. モジュールファイルからプリコンパイル済みモジュールを生成する
2. プリコンパイルモジュールからバイナリファイル(.o
)を生成
3. 2.
のバイナリファイルを取り込んで実行バイナリを生成
このような流れで最終的な実行バイナリを生成します。
また clang
でビルドする場合は -fmodules-ts
オプションが必要になります。
このビルドの仕方はここに書いてあるやり方を参考にしています。
他にも手段はあるんですかね?
あとこの仕組みだとコード補完や lint で使用する静的コード解析がどうなるのかが気になりますね。
プリコンパイル済みモジュールを読み込んで静的コード解析する感じになるんですかね?
その他の書き方
他にも以下のように namespace
を区切った書き方もできます。
C++17
// lib.h #ifndef LIB_H #define LIB_H #include <string> #include <iostream> namespace lib{ struct user{ std::string name; int age; }; inline std::ostream& operator <<(std::ostream& os, user u) { os << "name: " << u.name << ", age: " << u.age; return os; } inline int fact(int n){ return n > 1 ? n * fact(n - 1) : n; } } // namespace lib #endif /* LIB */
// main.cpp #include "./lib.h" int main(){ auto homu = lib::user{ "homu", 14 }; auto mami = lib::user{ "mami", 14 }; std::cout << homu << std::endl; std::cout << mami << std::endl; std::cout << lib::fact(5) << std::endl; return 0; }
C++20 + モジュール
// lib.cppm #include <string> #include <iostream> export module lib; // namespace 内の定義がエクスポート宣言される export namespace lib{ struct user{ std::string name; int age; }; inline std::ostream& operator <<(std::ostream& os, user u) { os << "name: " << u.name << ", age: " << u.age; return os; } inline int fact(int n){ return n > 1 ? n * fact(n - 1) : n; } } // namespace lib
// main.cpp #include <iostream> import lib; int main(){ auto homu = lib::user{ "homu", 14 }; auto mami = lib::user{ "mami", 14 }; std::cout << homu << std::endl; std::cout << mami << std::endl; std::cout << lib::fact(5) << std::endl; return 0; } /* output: name: homu, age: 14 name: mami, age: 14 120 */
慣れるまでは結構大変そうですがモジュールを使うと C++ の書き方がすごく変わりそうですね。
今までの概念ががらっと変わりそう…。
ここで書いたような例はモジュールのほんの一部でしかないので詳しくはこのスライドを読んでみるといいと思います。
まだ標準ライブラリとかには対応してないんですが、数年後の C++ は全然違うものになってそうですねー色んな意味で楽しみです。
参照
【一人 C++20 Advent Calendar 2019】実装依存の情報をまとめた <version> ヘッダーを追加【22日目】
一人 C++20 Advent Calendar 2019 22日目の記事になります。
実装依存の情報をまとめた ヘッダーを追加
実装依存の情報(実装固有のライブラリバージョンマクロなど)をまとめたヘッダーとして <version>
が追加されます。
今まではライブラリごとのヘッダーをインクルードする必要がありましたが <version>
をインクルードすることでライブラリ機能マクロなどをすべて使用できるようになります。
#include <version> #include <iostream> int main(){ // C++20 以前では対応するライブラリのヘッダーをインクルードする必要があった // C++20 では <version> をインクルードするだけですべて使用できるようになる std::cout << __cpp_lib_any << std::endl; std::cout << __cpp_lib_chrono << std::endl; std::cout << __cpp_lib_variant << std::endl; return 0; } /* output: 201606 201611 201606 */
ライブラリでワークアラウンド対応する場合に便利そう。
参照
【一人 C++20 Advent Calendar 2019】範囲ライブラリとして <ranges> が追加される【21日目】
一人 C++20 Advent Calendar 2019 21日目の記事になります。
今回もまだ実装しているコンパイラがないので軽めの内容です。
[範囲ライブラリとして が追加される]
今まではイテレータの組を利用してイテレーションを行っていたのですが、C++20 では新しく <ranges>
ライブラリが追加されます。
<ranges>
ではこれまでのイテレータ(std::begin
や std::end
)を関数に渡して使用するのではなくて配列に対して直接操作するような形でイテレーションする事が出来るようになります。
例えば filter
や transform
は以下のように書くことが出来るようになります。
#include <ranges> #include <iostream> #include <vector> int main(){ auto even = [](int n) { return n % 2 == 0; }; auto twice = [](int n) { return n + n; }; auto xs = std::vector{ 1, 2, 3, 4, 5, 6 }; for(int n // | でイテレーション処理を結合していく : xs | std::views:filter(even) // 引数の関数を元に絞り込み | std::views::transform(twice) // 引数の関数に要素を置き換える ) { std::cout << n << std::endl; } return 0; } /* output: 4 8 12 */
いわゆる PStade.Oven
や Boost.Range
みたいなやつですね。
同じく C++20 で追加される Concept をバリバリ使っているようなのでどうやって実装しているのかを調べてみると楽しそう
今まで C++ でイテレーション処理を行なうのがとてもめんどくさかったのでこれでかなり改善されるといいですねー。
参照
【一人 C++20 Advent Calendar 2019】std::result_of が削除される【20日目】
一人 C++20 Advent Calendar 2019 20日目の記事になります。
std::result_of が削除される
C++11 で追加され、C++17 で非推奨になっていた std::result_of
が削除されます。
変わりに今後は std::invoke_result
を使用する事が推奨されます。
#include <type_traits> struct plus{ template<typename T> auto operator ()(T a, T b) { return a + b; } }; int main(){ // C++20 ではエラー static_assert(std::is_same_v<std::result_of<plus(int, int)>::type, int>); static_assert(std::is_same_v<std::result_of<plus(float, float)>::type, float>); // C++20 では std::invoke_result を使用する static_assert(std::is_same_v<std::invoke_result<plus, int, int>::type, int>); static_assert(std::is_same_v<std::invoke_result<plus, float, float>::type, float>); return 0; }
result_of
は boost
を使っていた時代からお世話になっていたので削除されるのは感慨深いものがありますねえ。
std::invoke_result
といった今時のメタ関数は全然把握していたので C++20 とかで遊ぶ場合はどのメタ関数を使えばいいのかわからなくて苦労しそう。
参照
【一人 C++20 Advent Calendar 2019】新しいフォーマットライブラリ <format> が追加【19日目】
一人 C++20 Advent Calendar 2019 19日目の記事になります。
新しいフォーマットライブラリ が追加
printf
のようなフォーマットを指定して文字列を生成するライブラリが新しく追加されます。
例えば std::format(書式, 引数...)
のような形で呼び出し、関数の戻り値は std::string
になります。
まだ実装しているコンパイラがなかったのでここを参照して動作イメージを書いてみます。
基本的には書式内の {}
が引数の値に置き換わるような形で使用します。
// 必要なヘッダ #include <format> // {} で引数の値が展開される std::string result = std::format("name: {}, age: {}", "homu", 14); std::cout << result << std::endl; // output: name: homu, age: 14 // {} 内に数値を書くとその番号の引数で置き換える std::string result = std::format("age: {1}, name: {0}", "homu", 14); std::cout << result << std::endl; // output: age: 14, name: homu
他にも詳しく書式指定出来るので詳しくはこちらを参照して下さい。
またへんてこな書式を覚えるのかーってなるとめっちゃだるい…まあ慣れな気もしますが…。
とはいえ型安全らしいので便利といえば便利だと思います。
実際にライブラリが実装されてからいろいろと試してみたいところではありますねー。
参照
【一人 C++20 Advent Calendar 2019】<=> 演算子が追加される【18日目】
一人 C++20 Advent Calendar 2019 18日目の記事になります。
<=> 演算子が追加
C++20 では新しく三方比較演算子(<=>
) が追加されます。
宇宙船演算子とも言われてるやつですね。
<=>
演算子は
- 自分と等しい
- 自分よりも大きい
- 自分よりも小さい
の 3つの状態を返すための演算子で比較演算子で使用されることを想定しています。
実際に使うと以下のようになります。
#include <compare> #include <iostream> struct X{ // int 型の <=> は strong_ordering というオブジェクトを返す // これは 0 と比較する事で // 等しい // より大きい // より小さい // を表現することができる std::strong_ordering operator <=>(X const& other) const { return value <=> other.value; } // = default で定義することもできる // この場合は == 演算子も使用することができる // auto operator <=>(X const& other) const = default; int value; }; int main(){ constexpr auto x1 = X{1}; constexpr auto x2 = X{2}; std::cout << std::boolalpha; // 等しければ == 0 となる std::cout << ((x1 <=> x1) == 0) << std::endl; // 左辺の方が大きければ > 0 となる(正の値) std::cout << ((x2 <=> x1) > 0) << std::endl; // 左辺の方が小さければ < 0 となる(負の値) std::cout << ((x1 <=> x2) < 0) << std::endl; std::cout << "---" << std::endl; std::cout << (x1 < x2) << std::endl; std::cout << (x2 > x1) << std::endl; // エラー // 自前で <=> の処理を定義した場合は == 演算子は使用できない // = default で <=> を定義した場合は == 演算子を使用できる std::cout << (x1 == x2) << std::endl; return 0; }
実際に書いてみて気になったポイントとしては、
<=>
演算子はstd::strong_ordering
という専用のクラスを返す- 自前で
<=>
演算子を定義した場合は==
演算子で比較できない<=>
を= default
で定義した場合では==
演算子で比較できる
この 2点になります。
Ruby とかだと <=>
は 0
1
-1
みたいな数値を返すんですが、C++ の <=>
は std::strong_ordering
といった専用のオブジェクトを返します。
これは 0
と比較することで
- 等しい
- より大きい
- より小さい
という状態を調べることができます。
また、 <=>
演算子を = default
で定義したときのみ ==
演算子で比較することができます。
= default
を使用した場合は各メンバ変数を <=>
で比較した値を返します。
<=>
演算子は結構クセが強そうですが、定義しておくことで他の比較演算子を暗黙的に使用することができるようになるのは便利ですね。