ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
コンセプトでC++テンプレートを簡素化する
C++20機能が、C++コードを次のレベルに引き上げる仕組みをご覧ください。コンセプトを紹介し、そのコンセプトで一般的なC++コードのエラーをすばやく検出する方法について解説します。また、constexpr機能に関する最新の拡張機能を紹介し、これを活用した上で、コンパイル時にコードを評価してAppのパフォーマンスを向上させる方法についても解説します。
リソース
関連ビデオ
WWDC22
-
ダウンロード
♪ ♪
こんにちは Developer Tools 担当の Alex です Xcode 14でサポートする C++ 20の新機能を紹介します 特に C++ 20のコンセプトが 汎用C++コードの型安全性を いかに簡素化し向上させるか見ていきます コンセプトの使い方を実演し 独自のコンセプトの作り方も説明します 最後に Xcodeでサポートする C++20の新機能をいくつか 使って コンパイル時のコード評価の力で C++プロジェクトのパフォーマンスを 向上させる方法をお話しします
C++のコンセプトに取り組む前に C++で汎用コードを書く方法を説明します ある数字が 奇数かどうか 確認する関数を書く場合 int型のパラメータを受け取る関数を書けば int型で表現できるあらゆる値で動作します この関数に64ビットの 符号なし整数値を渡したら? このような具象関数はint型に収まるよう 切り捨てられ 64ビットの 値では正しく動作しません 解決するには isOddを 関数テンプレートにします 関数テンプレートができたので 64ビットの符号なし整数の値が渡せます コンパイラは uint64_t型で正しく動作する isOddの特殊化を 自動的に 生成するようになりました 2つの異なる型に対してそれぞれisOddを 書かなくて済むのでとても便利です C++のテンプレートでisOddのような汎用関数や 汎用コンテナクラスも書けます isOddの使い方を見ていきます この機能は複数のケースでテストされています 私はあるテストでミスしてしまいました コンパイラは 私がどこで 間違えたかを指摘する代わりに isOddテンプレートの内部でエラーを表示します 「11」ではなく「1.1」と書き間違えたようです そのため コンパイラはdouble型を取り込む isOddの特殊化を生成します 残念ながらisOddが誤った型で 起動された場所をXcodeが教えてくれず このtypoを見つけるのに時間がかかりました
言語やコンパイラを使えば 早く見つかるでしょうか? 今回の例では isOddにどのような型が許されるかという要件を 明示的に規定していません ドキュメントのコメントには 整数型を使って isOddを 呼び出すことが書いてあるだけです 以前は C++プログラマーが 汎用C++コードを書く際に テンプレート要件を指定する 良い方法がありませんでした 要件を指定する際には ドキュメントのコメントや 特定のパラメータ名 複雑なenable_ifチェックに 頼らざるを得ませんでした さて C++ 20には コンセプトという新機能を導入しています 汎用なC++のコードでテンプレートの要件が検証 できます isOddに渡せる 型の検証に コンセプトが どう役立つか見てみましょう まず isOddの宣言に戻ります 現在 classキーワードでTの型を指定しています Tは任意の型にできます C++20ではclassキーワードの 代わりに conceptを使って このテンプレートが使用する 型のセットが制限できます 標準ライブラリが提供する integralのコンセプトで isOdd関数テンプレートを 組み込みの整数型だけに 限定できます Tがコンセプトを満たさない場合 コンパイラはテンプレートを 特殊化しようとしません integralコンセプトはC++の標準ライブラリで 宣言しており 使用するには コンセプトヘッダーを含む 必要があります関数テンプレートisOddの T型に integral要件を 追加したので コンパイラは 私がテストで間違えた箇所を直接指摘する より明確な診断を提供できます 1.1はダブルで それゆえ integralコンセプトを 満たさないとわかりました コンパイラーが明確なエラー メッセージで説明するので 以前より早くタイポの発見と修正が可能です バグの修正に役立つだけでなく 渡される型をisOddに限定することで isOddのテストケースは すべて整数型のみで動作し 実際にそのアルゴリズムの意図した動作を テストしている安心感を与えてくれます コンセプトでテンプレートを どの型と一緒に使うか意図を宣言できます そして コンパイラはテンプレートが 特化される前に型要求の検証を行います コンセプトの使用の仕方やC++標準ライブラリが どのコアコンセプトを 提供するか見ていきましょう C++標準ライブラリでは コアコンセプトを提供します 型の核となる動作を検証するために使う 核となる言語コンセプトの セットを実装しています ライブラリにアクセスする には コードにコンセプト
ヘッダーを含めますintegralコンセプトの 使い方はもうお伝えしましたね このライブラリが提供する ほかのコンセプトを見ます このライブラリは 型が 組み込み型かどうかテスト するなど 便利なコア言語 コンセプトを多数提供します 例えば floatやdoubleなどの 組み込み型によって floating_pointコンセプトが満たされます static_assertは 本当に そのケースか 検証します ほかにも便利なコアコンセプトが多数あり 型が構成可能か破壊可能か 変換可能か 他の型と同じかなどをチェックします 例えば convertible_toはある型を 別の型に変換できるかテストします move_constructibleは同じ型の 別の値から 直接構築できる型によって満たされます このライブラリは型が他の型と比較できるか テストする比較コンセプトも提供します 例えば equality_comparableは 同じ型の値に対して有効な ==演算子を持つ型によって満たされます
これ以外にもこのライブラリでは コアな言語概念を多数提供しています 型が移動やコピー可能か テストするものもあります ある型が何らかの呼び出し 可能なオブジェクトか チェックするコンセプトもあります
C++の標準ライブラリが提供するコンセプトに 目を通したところで次は コンセプトでテンプレートを 制約する方法に移ります classキーワードの代わりにコンセプトを使って テンプレートが許可する型を制限できます さらに テンプレート宣言にrequiresを使うと ある型を複数のコンセプトに制約できます 何ができるのか変わった例で見てみましょう
ここに isDefaultValue関数 のテンプレートがあります 与えられた値がデフォルト値 と等しい場合trueを返します このテンプレートが特化 される前に この型が操作を サポートするかテストする ために 標準ライブラリから 2つの概念が使えますこの関数テンプレートで 許可される型を制限するため requiresを追加します コンセプトライブラリのどのコンセプトが 型の検証に役立つか確認していきます まず equality_comparableで Tが 同じ型の別の値と 比較できるかをテストします そして default_ constructibleが Tが デフォルトコンストラクタを 持つ型かテストします 両者の間にある論理演算子と演算子は コンパイラに両方の概念を 検証するよう指示します この関数テンプレートは これで サポートされている 型でのみ特化すると保証されます これまでのコンセプトをおさらいしましょう テンプレートが使用する型を制限するために コンセプトを使用しますこれにより コンパイラは 型の不一致が 発生しても テンプレートを 特殊化することなく診断結果を表示できます ある型の核となる動作を検証する場合は コンセプトライブラリの コンセプトを再利用します
型が複数の要件に適するかテストする場合は テンプレートにrequires節を追加します C++プログラムでのコンセプトの使い方を 見てきましたC++では 型の特定の動作を 検証する カスタムコンセプトを宣言できます では 特定の型の動作を検証する 独自のコンセプトの作成方法を説明します その前に宣言したいコンセプトで 検証されるべき動作要件の 特定の仕方を確認しましょう 新しい例としてコンセプトを用いて 特定の型の動作を検証する方法を説明します 例えば さまざまな2次元の図形を画像に レンダリングする C++の ライブラリを作るとします
ライブラリでさまざまな 形状をサポートしたいのです レンダリングが一番簡単な円形から始めます C++のクラスを使って位置や半径などの プロパティを保存します 円をレンダリングするため レンダリング画像の各ピクセルに実行される 距離関数ベースのレンダリングアルゴリズムを 使いますレンダリングするために 形状の表面までの距離を計測 します Circleクラスの getDistanceFromメソッドで計算します 円の内側の距離は負を 円の外側の距離は正を返します 円以外の他の図形もレンダリングします 例えばある円形と別の円形を 幾何学的に引き算して三日月型も表現できます 三日月のようにレンダリングしたい形状も クラスで表現します 新しいクラスに それぞれ getDistanceFromメソッドが 含まれ シェイプクラスの作成後に 形状をレンダリングして 実装を検証していきます どんな形状にも対応する レンダリング機能の作成には いくつか選択肢があります 形状のクラス階層を作り形状の 表面までの距離を計算する 仮想メソッドが使えますが この関数がレンダリング中に 何百万回も呼び出される 仮想呼び出しのオーバーヘッドを避けるため 代わりに関数テンプレートを使うつもりです このレンダリング機能の テンプレートを作りました computePixelColor関数は形状の値を取り込み ピクセルがその形状の中にあるか確認します 中にある場合は真っ白な色を返します これで 図形が正しく 塗りつぶせるか確認できます
この関数はテンプレートなので 円や三日月など どのような形状でもマッチングさせられます ここではテンプレートが うまく機能しますがこの機能に 渡せる型を制限するために コンセプトを使用します 制限することでコンパイラは 型の不一致が発生したときに より明確な診断が下せます それだけでなく 関数に渡す型を制限することで この関数のオーバーロードを 追加することもできます
型を制限するために Shape というコンセプトを作ります このコンセプトは型の動作を検証し ircleやcrescentなどのクラスや 将来追加したい 他の形状クラスも受け入れます Shapeのようなコンセプトを 作るには まずこのコンセプトで検証する 要件を特定する必要があります その方法を紹介します この関数テンプレートは 汎用型としてT型を使います T型の shapeという引数がこの関数に渡されます この引数Shapeは関数内で getDistanceFromメソッドを 呼び出すときに使用します この関数では Shapeに対して 他の操作は行われていないので 私のコンセプトで検証したい のはこの要件のみです requires式を使うとある型が特定の振る舞いを するかどうかテストできます requiresでShapeの概念を 作る方法を見ていきましょう requires内の型の動作をテストする 一連の式の提供が必要です getDistanceFromの呼び出しは テストする1つの要件として特定されているので Shapeコンセプトの作成に移れます conceptキーワードで 形状 コンセプトを宣言しました コンセプトにrequires式を 追加し 型を検証しました requires式に 引数リストを追加しました この引数リストでT型の値 Shapeを宣言して requestsの内部でテストできます requires式の引数リストで 任意の型の値を宣言できます これらの値が requestsの 内部で使えるようになります requires表現の主部には このコンセプトを満たすため 通過しなければならない一連の要件が含まれます shapeコンセプトにはgetDistanceFromが 呼び出し可能かチェックする シンプルな式が必要です この式は プログラムの中で実行されません コンパイル時に型の動作を検証するために のみ必要とされ検証後は破棄されます 式の要件を使って 特定の 式がコンパイルされるか テストすることで型の動作を検証します この式はまだ完全ではなく getDistanceFromメソッド 呼び出しの引数を加える必要があります このメソッドは float型の 2つの値を取りたいのです 2つの浮動小数点数リテラル を使い 式を完成します getDistanceFromメソッドが float値を返すことを テストする追加チェックを 加えます 私の汎用コードで 想定されているからです 現在 型に getDistanceFromメソッドがあるか テストするための 簡単な 式の要件を使っていますが 式の要件の代わりに複合要件を使用して float値を返すかどうかテストできます アローオペレーターは複合要件に従えます 矢印演算子は 右辺の制約を想定しているので same_asのような標準ライブラリの概念を 用いて getDistanceFromメソッドの呼び出しが float値を返すか検証できます このコンセプトが出来上がったようです
これを使ってcomputePixelColor関数に 渡す型を制限できます これで私の汎用computePixelColor関数は Shapeコンセプトを満たす型でのみ動作します つまり CircleやCrescentのようなクラスは computePixelColor関数でレンダリングされます 両方とも Shapeコンセプトを満たすからです
レンダリングされたプレーンな図形の確認後 図形に色を追加するcomputePixelColorの 別バージョンを作成します GradientCircleクラスを Shapeライブラリに加えます 画像の画素の色を計算する 新しい関数が必要です C++20では computePixel Color関数テンプレートの バリエーションが複数作成できます バリアント毎に 異なるコンセプトで制約します GradientCircleクラスを満たすような 新しいGradientShapeのコンセプトを作ります グラデーションを持つ形状にのみ作用して computePixelColorの新しい バリアントを制約します このコンセプトはShapeのコンセプトと同様 requests式を使って実装しています GradientShapeは本来のShapeコンセプトも 満たしたいので新しいコンセプトの 最初の要件として盛り込みました これで GradientShape コンセプトを満たすクラスは Shapeコンテストも満たすので それらのクラスの値に対して getDistanceFromメソッドを 呼び出すことができます そして 論理積演算子とrequires式を使って getGradientColorメソッドを持つクラスだけが GradientShapeコンセプトを満たすようにします GradientShapeコンセプト作成の次は computePixelColorの 新しいバリアントを作ります この関数テンプレートは GradientShapeコンセプトに 制約され GradientCircleクラスのような グラデーションを持つShape クラスでのみ動作します 材料がすべて揃ったので 円をグラデーションでレンダリングしてみます GradientCircleをレンダリングしています コンパイラがはrender関数内で computePixelColorの どの オーバーロードを選ぶのか
GradientCircleはcomputePixelColorの 両方のバージョンで安全に使用できますが コンパイラは 最初の オーバーロードより具体的な GradientShapeコンセプトで 制約されたものを選びます computePixelColorの最も マッチするオーバーロードを 選ぶので この美しい円がレンダリングされます 素晴らしい!
ここまでのコンセプトの 作り方をおさらいしましょう
既存の汎用コードに含まれる 動作要件を特定することで コンセプトが作成できます 型の動作を検証するコンセプトを作るには requests式を使用します また コンセプトを利用して 汎用的な関数やクラスの 具体的なバリエーション作成も可能です
汎用的なC++のコードを コンセプトで強化する方法を 見てきましたXcode 14では加えて 他のC++20の機能に対する サポートも改善しました 具体的には Xcode 14ではコンパイル時の C++コード評価のサポートを 強化したことに注目します コンパイル時のコード評価は C++コードの変数に対する初期化コストの 削減に有効です 複雑な初期化シーケンスに依拠する C++コードが多いAppの場合 App起動時間の短縮に役立つ可能性が高いです コンパイル時にコード評価を行うことで コンパイル時に検証が必要な 定数の検証も行えます よって コードの実行前に バグの発見もできるでしょう C++での コンパイル時の コード評価の使用例を見ていきます
私のShapeレンダリングライブラリで カラー パレットを初期化するコード スニペットを用意しました このライブラリは 形状を ディスプレイにレンダリング する iOSAppで使用します パレットの各色は 色の HTML16進コードを含む 文字列リテラルをパースして 初期化します fromHexCode 関数は 配列の初期化時に 3つの文字列リテラルを パースする必要があります このような複雑な定数 初期化操作が多数ある場合 Appの起動時間に 測定可能な 影響を与えることがあります コンパイル時のコード評価でこの配列が 定数色値で初期化されるようにできます これからお見せしますconstexprキーワードは C++のコンパイル時のコード評価を可能にします パレットが一定の色配列になるように この例では 数カ所にこれを 追加する必要があります まず fromHexCode関数にconstexprキーワードを 追加します コンパイル時の 初期化シーケンスで使うと この関数のコードを コンパイル時に実行できます C++の関数を コンパイル時に評価したい場合は それらを constexprにする必要があります コンパイラは関数内のコードが コンパイル時に評価できない 場合 constexpr初期化 シーケンスで使用すると エラー表示して知らせますが constexprをつける前にその間数が コンパイル時に評価可能か 調べることもできます このような関数がコンパイル時の コード評価の良い候補になりうるか fromHexCodeで確認してみましょう この関数ではif文のような言語構成と 比較演算子や 算術演算子のような原始的な演算を使用します この演算はすべてコンパイル時に評価できます この関数は別の関数hexToInt を複数回呼び出します hexToInt関数にはアノテーションを付けている ので この関数の呼び出しは コンパイル時に評価できます 全体として fromHexCodeには コンパイラがコンパイル時に 評価できるコードが含まれて おり コンパイル時の 初期化シーケンスに使用して 問題ないと思います fromHexCodeがコンパイル時に評価されることを 確認したあとconstexprキーワードを colorPalette変数の宣言に 追加する必要があります これで コンパイル時に この配列の初期化シーケンス 全体を評価することを 保証するようになりました 具体的には コンパイラはfromHexCode関数の 各呼び出しを評価します この評価が パレットのイニシャライザで 元の 関数の呼び出しを置き換える 定数色の値を生成します fromHexCodeの呼び出しは 定数色値に置き換えられる ので 変数colorPaletteは定数色値を含む 配列リテラルで 初期化 されることが保証されます つまり このパレットが 初期化されるときに Appが 色の値を解析するための追加コストは不要です Appの起動時に App内のC++ライブラリが 行う作業を減らせるため 私のAppの起動時間にとって素晴らしいことです C++の変数が定数値で初期化されることを 確認する場合は constexprに する必要があります Xcode 14ではコンパイル時の評価に関する 標準ライブラリのサポートを 大幅に改善しています 今年は いくつかの異なる標準ライブラリの型や アルゴリズムにconstexprサポートを追加し コンパイル時のコード評価に 使えるようになりました
加えて Xcode 14ではC++20標準のサポートを 大幅に改善しました ここで紹介した機能はすべて C++ 20モードで使用できます
まだの方は 今日中にC++ 20 モードに切り替えてください XcodeプロジェクトのC++ Language Dialect 設定を使って C++ 20にアップグレードできます C++20では コードの中で コンセプトのような機能が 使えます C++20は最小のデプロイターゲットを 必要としないので現在のターゲットと同じ OSバージョン用にコードを出荷できます C++20を今すぐお試しください ありがとうございました! 残りのWWDCもお楽しみください
-
-
0:02 - snippet1
int main() { }
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。