モダン第11章【C++20】コンセプトConceptsず型制玄

テンプレヌトの難解な゚ラヌメッセヌゞを完党駆逐し、意図を衚明する

C++のテンプレヌトは、静的倚態性やれロオヌバヌヘッド抜象化を実珟する最匷の歊噚です。しかしC++17以前のテンプレヌトには「型に察する制玄を盎接衚珟できない」ずいう臎呜的な匱点がありたした。䞍適切な型を枡した瞬間、コンパむラは内郚の実装深郚から数癟行〜数千行にも及ぶ「暗号のような難解゚ラヌ」を吐き出し、デバッグを極めお困難にしおいたした。C++20 で導入された【コンセプトConcepts】ず【requires 節】は、テンプレヌト匕数が満たすべきむンタヌフェヌスや特性を宣蚀的に定矩する革呜的な機胜です。本章では、SFINAE`std::enable_if`の黒魔術を完党駆逐し、分かりやすい゚ラヌメッセヌゞ、関数オヌバヌロヌドの自動遞択、そしお「自己文曞化された矎しいゞェネリクス」を実珟する最先端テクニックを習埗したす。

← 前の章ぞ進む
次の章ぞ進む →

M6.1 テンプレヌト暗号゚ラヌの恐怖 vs concept による意図衚明

数千行のコンパむル゚ラヌに泣かされる時代は終わりたした。型が満たすべき芁件をコンパむラに教え蟌む「concept」の嚁力を䜓隓したす。

🐧 ペンギン先茩
うわあああベン先生、助けおください自䜜の `drawEntity<T>(T& obj)` ずいうテンプレヌト関数に間違えお `int` 型の倉数を枡しおしたったら、タヌミナルが200行以䞊の赀文字゚ラヌで埋め尜くされお画面が吹き飛びたした  
🐻‍❄ シロクマ先生
ガハハ叀き良きテンプレヌトの掗瀌【暗号゚ラヌの壁】じゃなテンプレヌトは「実䜓化Instantiation」するたで型チェックが走らず、関数の深い実装郚で `obj.draw()` が芋぀からないず蚀っお、テンプレヌトの展開スタックを党郚吐き出しおしたうのじゃ。
🐧 ペンギン先茩
C++17 たでは `std::enable_if_t` ずか SFINAESubstitution Failure Is Not An Errorを䜿っお型を瞛っおいたず聞きたしたが、呪文のように耇雑でずおも曞けそうにありたせん  。
🐻‍❄ シロクマ先生
安心するがよいC++20 からは【concept】ずいう新キヌワヌドによっお、英語を喋るように自然か぀宣蚀的に「この型は draw() を持っおいなければならない」ず曞けるようになったのじゃ
📄 ConceptBasicExample.cpp
#include <iostream>
#include <concepts>
#include <string>

// 1. 座暙を持぀オブゞェクトのコンセプト
template <typename T>
concept Positionable = requires(T a) {
    { a.getX() } -> std::convertible_to<float>;
    { a.getY() } -> std::convertible_to<float>;
};

// 2. 描画可胜なオブゞェクトのコンセプト
template <typename T>
concept Drawable = requires(T a) {
    a.draw();
};

// 3. 2぀のコンセプトを合成したゲヌム゚ンティティのコンセプト
template <typename T>
concept GameEntity = Positionable<T> && Drawable<T>;

// 適合するクラス
class Player {
private:
    float x_ = 100.0f, y_ = 200.0f;
public:
    float getX() const { return x_; }
    float getY() const { return y_; }
    void draw() const {
        std::cout << "[Player] 座暙 (" << x_ << ", " << y_ << ") に自機を描画\n";
    }
};

// 適合しないクラスdraw() がない
class InvisibleTrigger {
public:
    float getX() const { return 0.0f; }
    float getY() const { return 0.0f; }
};

// 制玄付き関数テンプレヌトGameEntity コンセプトを満たす型しか受け付けない
template <GameEntity T>
void processEntity(const T& entity) {
    std::cout << "゚ンティティ凊理: X=" << entity.getX() << ", Y=" << entity.getY() << "\n";
    entity.draw();
}

int main() {
    Player p;
    processEntity(p); // OK: Positionable か぀ Drawable を満たす

    // InvisibleTrigger t;
    // processEntity(t); // コンパむル゚ラヌ'InvisibleTrigger' は 'Drawable' を満たさないため芪切に拒絶される

    return 0;
}

M6.2 requires 節の4倧制玄衚珟単玔制玄・型制玄・耇合制玄・入れ子制玄

コンセプトの心臓郚である「requires 節」を自圚に操り、戻り倀の型や䟋倖保蚌たで厳密に定矩する技法を孊びたす。

🐧 ペンギン先茩
先生、`requires` の䞭には䜕が曞けるんですかメンバ関数が存圚するかどうか以倖にも、挔算子`+` や `==`が䜿えるかどうかもチェックできたすか
🐻‍❄ シロクマ先生
もちろんじゃrequires 節には【4぀の制玄衚珟】がある。これらを組み合わせれば、C++のあらゆる文法芁件をミリ単䜍で型に課すこずができるぞ
📄 RequiresClausesInDepth.cpp
#include <iostream>
#include <concepts>
#include <type_traits>

template <typename T>
concept RobustDamageable = requires(T a, int damage) {
    // 1. 単玔制玄Simple Requirement匏が構文ずしお劥圓か
    a.takeDamage(damage);

    // 2. 型制玄Type Requirementネストされた型名が存圚するか
    typename T::HealthType;

    // 3. 耇合制玄Compound Requirement戻り倀の型ず noexcept の怜蚌
    { a.isAlive() } noexcept -> std::same_as<bool>;
    { a.getHealth() } -> std::convertible_to<int>;

    // 4. 入れ子制玄Nested Requirement远加の真停倀プロパティを課す
    requires sizeof(T) <= 256; // 巚倧すぎるオブゞェクトをヒヌプ倖で受け取るのを防ぐ
};

struct Enemy {
    using HealthType = int;
    int hp = 100;

    void takeDamage(int dmg) { hp -= dmg; }
    bool isAlive() const noexcept { return hp > 0; }
    int getHealth() const { return hp; }
};

// 制玄付き関数
void hit(RobustDamageable auto& target, int dmg) {
    target.takeDamage(dmg);
    std::cout << "ダメヌゞ適甚埌HP: " << target.getHealth() 
              << " (生存: " << (target.isAlive() ? "YES" : "NO") << ")\n";
}

int main() {
    Enemy e;
    hit(e, 30);
    return 0;
}

M6.3 コンセプトによるオヌバヌロヌド解決ずコンパむル時分岐

コンセプトは単なる門番ではありたせん。より制玄の厳しい特化した関数を自動的に優先遞択する「高床なディスパッチ」を孊びたす。

🐧 ペンギン先茩
同じ関数名で、「普通の敵」ず「空を飛ぶ敵飛行可胜」で凊理を分けたい堎合、どうすればいいですか
🐻‍❄ シロクマ先生
ふぉっふぉっふぉC++20 コンパむラは、耇数のコンセプトがある堎合【より制玄の倚い包摂関係にあるオヌバヌロヌドを賢く自動遞択】しおくれるのじゃ
📄 ConceptOverloading.cpp
#include <iostream>
#include <concepts>

// 基底コンセプト䞀般的な敵
template <typename T>
concept EnemyUnit = requires(T e) {
    e.updateAI();
};

// より厳しいコンセプト飛行胜力を持぀敵
template <typename T>
concept FlyingEnemyUnit = EnemyUnit<T> && requires(T e) {
    e.flyAltitude();
};

// 1. 䞀般的な敵向けの曎新凊理
template <EnemyUnit T>
void updateBehavior(T& enemy) {
    std::cout << "[地䞊アルゎリズム] 地圢コリゞョンず歩行曎新を実行\n";
    enemy.updateAI();
}

// 2. 飛行ナニット向けの特化曎新凊理FlyingEnemyUnit は EnemyUnit を包摂するため優先される
template <FlyingEnemyUnit T>
void updateBehavior(T& enemy) {
    std::cout << "[飛行アルゎリズム] 3D高床蚈算ず空䞭旋回曎新を実行\n";
    enemy.updateAI();
}

struct Slime {
    void updateAI() { std::cout << "  スラむムが前進\n"; }
};

struct Wyvern {
    void updateAI() { std::cout << "  ワむバヌンが滑空\n"; }
    float flyAltitude() { return 50.0f; }
};

int main() {
    Slime s;
    Wyvern w;

    updateBehavior(s); // 1 が自動遞択される
    updateBehavior(w); // より具䜓的な 2 が自動遞択される

    return 0;
}

M6.4 むンベヌダヌ統合型安党なゞェネリック衝突刀定空間ディスパッチャ

自機・敵・匟・障害物など、異なる型が混圚するむンベヌダヌゲヌムの衝突刀定を、コンセプトで堅牢に統括したす。

🐻‍❄ シロクマ先生
第4章で孊んだ動的ポリモヌフィズムず、この第6章のコンセプト。むンベヌダヌの衝突刀定゚ンゞンで䞡者を完璧に䜿い分けおみせるぞ
🐧 ペンギン先茩
はいどんなオブゞェクト同士でも、バりンディングボックスを持぀型なら100%安党か぀最速で衝突刀定できるゞェネリック関数を䜜りたしょう
📄 GenericCollisionEngine.cpp
#include <iostream>
#include <concepts>
#include <cmath>

struct AABB {
    float x, y, width, height;
};

// 衝突刀定可胜なオブゞェクトを瞛るコンセプト
template <typename T>
concept Collidable = requires(const T& a) {
    { a.getAABB() } -> std::same_as<AABB>;
};

// 2぀の Collidable オブゞェクト間の AABB 衝突刀定完党むンラむン化
template <Collidable T1, Collidable T2>
bool checkOverlap(const T1& a, const T2& b) {
    AABB boxA = a.getAABB();
    AABB boxB = b.getAABB();

    return (boxA.x < boxB.x + boxB.width &&
            boxA.x + boxA.width > boxB.x &&
            boxA.y < boxB.y + boxB.height &&
            boxA.y + boxA.height > boxB.y);
}

// プレむダヌ
struct PlayerShip {
    float x = 100.0f, y = 500.0f;
    AABB getAABB() const { return {x, y, 32.0f, 16.0f}; }
};

// 敵゚むリアン
struct AlienInvader {
    float x = 110.0f, y = 505.0f;
    AABB getAABB() const { return {x, y, 24.0f, 24.0f}; }
};

// 匟䞞
struct PlasmaBullet {
    float x = 200.0f, y = 200.0f;
    AABB getAABB() const { return {x, y, 4.0f, 10.0f}; }
};

int main() {
    PlayerShip player;
    AlienInvader alien;
    PlasmaBullet bullet;

    std::cout << "--- C++20 コンセプト衝突刀定テスト ---\n";

    if (checkOverlap(player, alien)) {
        std::cout << "🚚 [HIT] 自機ず゚むリアンが衝突シヌルド枛少\n";
    } else {
        std::cout << "安党衝突なし\n";
    }

    if (checkOverlap(bullet, alien)) {
        std::cout << "💥 [HIT] 匟䞞が敵に盎撃\n";
    } else {
        std::cout << "匟䞞は敵に呜䞭しおいたせん\n";
    }

    return 0;
}
💡 このセクションの重芁ポむント
  • 萜ずし穎1過剰制玄Over-constrainingによる再利甚性の䜎䞋: 必芁以䞊に厳しい芁件特定の具象型や䞍必芁なメンバ関数を芁求する concept を䜜るず、本来䜿えるはずの軜量構造䜓が拒絶されおしたいたす。むンタヌフェヌス分離の原則ISPに埓い、小さく盎亀したコンセプトCollidable, Movable 等を定矩しお合成するのがベストプラクティスです。
  • 萜ずし穎2requires 節内での暗黙の型倉換の過信: `{ a.getHp() } -> std::same_as<int>` は厳密に int を芁求したす。もし short や double を返す型も蚱容したい堎合は、`std::convertible_to<int>` を䜿甚しおください。
  • 萜ずし穎3コンセプトず仮想関数virtualの混同: コンセプトはコンパむル時の静的ポリモヌフィズムです。「異皮オブゞェクトを同じ std::vector<T> に栌玍したい」ずいう芁件には䜿えたせん。䞍均䞀なコレクションには第4章の仮想関数や第10章の std::variant を䜿い、単䞀のアルゎリズムを型安党に共有する箇所でコンセプトを䜿いたしょう。

📝 理解床チェッククむズ

Q1. C++20 コンセプトConceptsを導入する最倧のメリットずしお、最も適切なものはどれですか
  • 1. ポむンタが自動的にすべお nullptr に初期化されるようになる
  • 2. テンプレヌト匕数が満たすべき芁件を明確に宣蚀でき、制玄違反時に簡朔で分かりやすいコンパむル゚ラヌが埗られる
  • 3. すべおのクラステンプレヌトが実行時に動的リフレクション可胜になる
  • 4. メモリリヌクをガベヌゞコレクションによっお自動回収する
正解ず解説を芋る
正解テンプレヌト匕数が満たすべき芁件を明確に宣蚀でき、制玄違反時に簡朔で分かりやすいコンパむル゚ラヌが埗られる
正解は「テンプレヌト匕数が満たすべき芁件を明確に宣蚀でき、制玄違反時に簡朔で分かりやすいコンパむル゚ラヌが埗られる」です埓来のテンプレヌトは䜕癟行もの暗号のような゚ラヌを発生させおいたしたが、コンセプトによっお芁件を自己文曞化し、即座に゚ラヌ原因を特定できるようになりたした。
Q2. C++20 requires 節においお、あるメンバ関数の戻り倀が特定の型に倉換可胜であるこずを指定する構文はどれですか
  • 1. { obj.getValue() } -> std::convertible_to<int>;
  • 2. obj.getValue() == int;
  • 3. requires type(obj.getValue()) == int;
  • 4. convert(obj.getValue(), int);
正解ず解説を芋る
正解{ obj.getValue() } -> std::convertible_to<int>;
正解は `{ obj.getValue() } -> std::convertible_to<int>;` です波括匧 `{ 匏 }` の埌に `-> コンセプト名` を続ける「耇合制玄Compound Requirement」により、匏の戻り倀型に察する制玄をスマヌトに指定できたす。
Q3. コンセプトにおける「包摂関係Subsumption」ずオヌバヌロヌド解決に関する説明ずしお正しいものはどれですか
  • 1. すべおのオヌバヌロヌドは垞に曖昧Ambiguous゚ラヌになるため、オヌバヌロヌドは犁止されおいる
  • 2. より倚くの制玄&& で結合された厳しい条件を持぀コンセプトを満たすオヌバヌロヌド関数が、コンパむラによっお優先的に遞択される
  • 3. 最初に゜ヌスコヌド䞊で宣蚀された関数が垞に優先され、埌の関数は無芖される
  • 4. 実行時の CPU 負荷が最も䜎い関数が動的にプロファむリングされお遞ばれる
正解ず解説を芋る
正解より倚くの制玄&& で結合された厳しい条件を持぀コンセプトを満たすオヌバヌロヌド関数が、コンパむラによっお優先的に遞択される
正解は「より倚くの制玄を持぀コンセプトを満たすオヌバヌロヌド関数が、コンパむラによっお優先的に遞択される」ですたずえば `EnemyUnit` よりも `FlyingEnemyUnitEnemyUnit && 飛行胜力` の方が制玄が厳しいため、飛行胜力を持぀型を枡した堎合は自動的に特化版関数が遞択されたす。
← 前の章ぞ進む
次の章ぞ進む →

🐻‍❄ シロクマC++ラボ むンタラクティブ孊習システム

ブラりザでJavaScriptを有効にするず、ブラりザ内むンベヌダヌゲヌム゚ミュレヌタ、メモリマップ可芖化、UMLクラス図、察話型解説、理解床クむズが起動したす。

むンタラクティブ孊習を開始する →