WWDC Quick Look 💓 By SwiftGGTeam
Simplify C++ templates with concepts

Simplify C++ templates with concepts

元の動画を見る

ハイライト

C++20 概念では、ジェネリック コードのエラー情報をテンプレート内から呼び出しサイトに転送でき、コンパイラはインスタンス化の前に一致しない型を拒否します。 constexpr コンパイル時評価を使用すると、実行時の初期化オーバーヘッドをコンパイル段階に進めることもできます。

主要内容

テンプレートのエラー報告は頭の痛い問題です

C++ テンプレートを作成する場合の一般的なシナリオ: 関数isOdd当初の目的は整数のみを受け入れることでしたが、テスト コードは手動で記述されました。1.1の代わりに11。コンパイラ エラーはテンプレートの内部を指しており、次のようになります。%演算子は一緒に使用できませんdouble。開発者は、呼び出しサイトで実際のエラーを見つけるために、テンプレート拡張情報のレイヤーを調べなければなりません。

C++20 より前では、テンプレート パラメーターはコメント、パラメーターの命名規則、または複雑なパラメーターによってのみ制約できました。enable_if。これらのメソッドにはコンパイラ チェックがないか、エラー メッセージが依然として不明瞭です。

概念: インスタンス化の前にエラーをインターセプトする

C++20 では、テンプレートの宣言時にパラメーターの型が満たさなければならない条件を指定できるようにする概念が導入されています。コンパイラは、テンプレートをインスタンス化する前に概念をチェックし、一致するものがない場合は、呼び出し側で直接エラーが報告されます。

詳細

標準ライブラリのコンセプトを使用してテンプレートを制約する

03:56

#include <concepts>

// 以前:どの型でも渡せた
template <class T>
bool isOdd(T value) {
    return value % 2 != 0;
}

// 現在:整数型だけを受け入れる
template <std::integral T>
bool isOdd(T value) {
    return value % 2 != 0;
}

// テストコード
isOdd(11);        // 成功
isOdd(1.1);       // コンパイルエラー:double は integral concept を満たさない

キーポイント:

  • std::integralC++ 標準ライブラリによって提供される概念であり、<concepts>ヘッダーファイル内
  • classキーワードの代わりにstd::integralを使用します。テンプレートが組み込みの整数型のみを受け入れるように制限します。
  • doubleを渡すと、コンパイラはテンプレート内部ではなくisOdd(1.1)の行で直接エラーを報告します

標準ライブラリの概念の概要

06:04

<concepts>ヘッダー ファイルは、次のような主要な概念セットを提供します。

#include <concepts>

// 型コンセプト
static_assert(std::floating_point<float>);      // float は満たす
static_assert(std::floating_point<double>);     // double は満たす

// 構築可能/破棄可能
static_assert(std::destructible<int>);
static_assert(std::move_constructible<int>);

// 型変換
static_assert(std::convertible_to<int, float>); // int は float に変換可能

// 比較可能
static_assert(std::equality_comparable<int>);   // int には == 演算子がある

キーポイント:

  • floating_point: 浮動小数点型かどうかをテストします。
  • convertible_to: 型 A を暗黙的に型 B に変換できるかどうかをテストします。
  • move_constructible: 同じ型の値から移動コンストラクターを構築できるかどうかをテストします
  • equality_comparable: 有効な値があるかどうかをテストします。==オペレーター

require 句: 複数の条件制約

08:22

単一のコンセプトでは不十分な場合は、requires句は複数の条件を組み合わせます。

#include <concepts>

template <typename T>
requires std::equality_comparable<T> && std::default_constructible<T>
bool isDefaultValue(T value) {
    return value == T{};  // == とデフォルト構築が必要
}

キーポイント:

  • requires句はテンプレートパラメータリストの後に配置されます
  • &&を使用して複数のコンセプトを組み合わせる場合、すべての条件が同時に満たされる必要があります
  • これにより、型が必要な操作をサポートする場合にのみテンプレートがインスタンス化されるようになります。

カスタム コンセプト: 式を使用する

13:46

さまざまな形状のグラフィックス レンダリング ライブラリがあるとします。getDistanceFrom方法。を定義できますShape concept:

// カスタム Shape concept
template <typename T>
concept Shape = requires(T shape) {
    { shape.getDistanceFrom(0.0f, 0.0f) } -> std::same_as<float>;
};

// concept でテンプレートを制約
template <Shape T>
Color computePixelColor(const T& shape, float x, float y) {
    float distance = shape.getDistanceFrom(x, y);
    return distance < 0 ? Color::white : Color::black;
}

// Circle クラスは Shape concept を満たす
class Circle {
public:
    float getDistanceFrom(float x, float y) const {
        float dx = x - centerX;
        float dy = y - centerY;
        return std::sqrt(dx * dx + dy * dy) - radius;
    }
private:
    float centerX, centerY, radius;
};

キーポイント:

  • conceptキーワードは新しい概念を定義します
  • requires式内の引数リストはテストする値を宣言します
  • 表現要件{ ... }特定のコードがコンパイルできるかどうかをテストする
  • -> std::same_as<float>複合要件で戻り値の型を確認する
  • 式はコンパイル時にのみ検証され、実際には実行されません。

コンセプトのオーバーロード: 最適な関数を自動的に選択します

18:20

異なる概念制約を持つ関数はオーバーロードを形成する可能性があり、コンパイラーは最も具体的なバージョンを自動的に選択します。

// 基本形状の concept
template <typename T>
concept Shape = requires(T shape) {
    { shape.getDistanceFrom(0.0f, 0.0f) } -> std::same_as<float>;
};

// グラデーション形状の concept(Shape も満たす)
template <typename T>
concept GradientShape = Shape<T> && requires(T shape) {
    { shape.getGradientColor(0.0f, 0.0f) } -> std::same_as<Color>;
};

// 基本版:すべての形状
template <Shape T>
Color computePixelColor(const T& shape, float x, float y) {
    return shape.getDistanceFrom(x, y) < 0 ? Color::white : Color::black;
}

// グラデーション版:グラデーションを持つ形状
template <GradientShape T>
Color computePixelColor(const T& shape, float x, float y) {
    float dist = shape.getDistanceFrom(x, y);
    return dist < 0 ? shape.getGradientColor(x, y) : Color::black;
}

// GradientCircle は Shape と GradientShape の両方を満たす
// コンパイラーは 2 つ目の、より具体的なオーバーロードを選ぶ
GradientCircle circle;
auto color = computePixelColor(circle, 1.0f, 2.0f);

キーポイント:

  • GradientShape継承するShape要件が満たされていることを確認するための要件GradientShapeタイプは満たす必要がありますShape
  • コンパイラは、オーバーロードの解決中に制約の最も具体的なバージョンを選択します。
  • これは SFINAE テクニックよりもはるかにシンプルで直感的です。

constexpr: コンパイル時の評価により起動時のオーバーヘッドが削減されます

21:09

// コンパイル時に色を解析
constexpr Color fromHexCode(const char* hex) {
    // #RRGGBB 文字列を解析
    return Color{hexToInt(hex, 0), hexToInt(hex, 2), hexToInt(hex, 4)};
}

// カラーパレットをコンパイル時に初期化
constexpr Color colorPalette[] = {
    fromHexCode("#FF0000"),  // コンパイル時評価
    fromHexCode("#00FF00"),
    fromHexCode("#0000FF")
};

キーポイント:

  • constexprマークされた関数はコンパイル時に実行できます
  • constexpr変数はコンパイル時に初期化されることが保証されています
  • Xcode 14 は標準ライブラリの constexpr サポートを強化し、より多くの型とアルゴリズムをコンパイル時に使用できるようになります。
  • コンパイル時の初期化により、アプリ起動時の実行時のオーバーヘッドが削減されます

C++20 へのアップグレード

26:29

Xcode 14 は C++20 を完全にサポートします。概念やその他の機能を使用するには、ビルド設定の「C++ 言語方言」を C++20 に変更します。 C++20 には展開ターゲットの最小要件はなく、サポートされている既存の OS バージョンに対してリリースできます。

重要ポイント

  • 既存のテンプレートにコンセプト制約を追加 プロジェクト内でよく使用される関数テンプレートとクラス テンプレートを見つけ、標準ライブラリの概念またはカスタムの概念を使用して型制約を追加します。エラーメッセージはすぐに分かり、メンテナンスコストが削減されます。

  • 複雑な SFINAE テクニックを概念に置き換えます コード内にある場合enable_ifvoid_tテンプレートのメタプログラミング手法が概念を使用して書き直されるまで待ちます。コードが読みやすくなり、コンパイル エラーが理解しやすくなります。

  • 起動時の定数の初期化をconstexprに変更 プロジェクト内の定数配列、ルックアップ テーブル、構成データを確認します。 constexpr を使用してコンパイル時に初期化ロジックを実行できる場合、起動時間が短縮されます。

  • ドメインモデルの概念システムを定義 たとえば、レンダリング エンジンの定義ShapeGradientShapeTexturedShapeなどのコンセプト。新しいタイプはコンセプトを満たす限りシームレスに接続でき、インターフェース文書はコードです。

関連セッション

コメント

GitHub Issues · utterances