
2026/03/10 19:41
**型消去(Type Erasure)の導出** - **目的** - 実行時に型パラメータを除去してジェネリックコードを単純化する。 - ジェネリックとレガシー(非ジェネリック)コードの相互運用性を実現する。 - **仕組み** 1. 各型パラメータをその *上限*(境界)が無ければ `Object` に置き換える。 2. バイトコードからジェネリック署名を消去する。 3. 必要に応じて多相性を保つためブリッジメソッドを生成する。 - **結果** - 実行時にコンパイル時の型安全が失われる。 - 消えた型に依存する操作はキャストが必要になる。 - 不正なキャストが起これば `ClassCastException` が発生し得る。 - **実務上のヒント** - 型消去の影響を抑えるため、可能なら境界付きワイルドカード(`<? extends T>`・`<? super T>`)を使用する。 - ジェネリックロジックは別途ヘルパークラスに分離しておくとよい。 - キャストロジックをカプセル化した型安全ビルダーやファクトリーを利用する。
RSS: https://news.ycombinator.com/rss
要約▶
日本語訳:
(すべての重要ポイントを統合して明確に):**
要約
この記事では、
std::any が 型消去 を実装し、具体的な型を一貫したインターフェースの背後に隠す方法について説明しています。これを他の二つの多態性スタイルと比較します。
- 従来の継承による多態性 – 仮想メソッドを持つ共通ベースクラス(
)を使用し、実行時に動作するが明示的なボイラープレートが必要で、階層が深くなる可能性があります。Shape - テンプレートベースの多態性 – コンパイル時に解決され、仮想呼び出しを排除しますが、各型ごとに別々のテンプレートインスタンス化が必要で、コンパイル時間とバイナリサイズが増大します。単一の実行時ベースクラスが存在しないためです。
互いに関連のない型(
Square、Circle)を変更せずに共通インターフェース経由で使用できるようにするため、記事では Shape を継承したラッパークラスが保持オブジェクトへの呼び出しを転送する方法を示しています。ボイラープレートのラッパーは、テンプレート化された ShapeWrapper<T> を導入して回避され、コンパイル時に転送ロジックが生成されます。
型消去の核心は
実装で示され、各ラップされた図形をヒープ確保モデル(AnyShape
Model<T>)として保存します。実際のコードでは、小さなオブジェクトをインラインに格納してヒープ割り当てを減らす小バッファ最適化(SBO)がよく追加されます。記事はまた、標準レイアウトと同様の汎用 Any クラスをスケッチし、Concept ベース、テンプレート化された Model<T>、および f() などのメンバ関数の転送を示しています。
このパターンは他の STL の例(例:
std::function)と比較され、C++20 の概念とは実行時抽象化ではなくコンパイル時チェックを行うものとして明確に区別されています。記事の最後には Rust における型消去についての議論への参照が付けられています。
結論:
型消去(
std::any スタイルのラッパー)を使用することで、開発者は継承階層の冗長性やテンプレート多態性のコンパイル時コストを回避しつつ、異種型を受け入れる簡潔で柔軟な API を書くことができます。これにより、実行時多態性が必要なライブラリ作者とアプリケーションに恩恵があります。本文
2026年3月10日
std::any を見たことがありますか? その背後で何が起きているのか、気になったことはありませんでしたか?恐ろしいように見えるインターフェースの裏側には 型消去 (type erasure) と呼ばれる古典的な手法があります。具体的な型を小さく統一されたラッパーで隠すことで、実装は隠蔽されます。仮想関数やテンプレートといった馴染みのあるツールから始めて、最小限の
std::any を構築してみましょう。最後には、型消去が内部でどのように機能するかを明確に理解できるはずです。
インターフェースによる多態性
多態性を実現する典型的な方法は、呼び出したい純粋仮想メソッドからなるインターフェースを定義し、各実装ごとにその基底クラスを継承してメソッドを実装することです。
例えば、
area() メソッドを持つ図形クラスを実装してみましょう。
// インターフェース class Shape { public: virtual ~Shape() = default; virtual auto area() const noexcept -> double = 0; }; // 実 concrete クラス class Square : public Shape { int side_; public: explicit Square(int side) noexcept : side_{side} {} auto area() const noexcept -> double override { return side_ * side_; } }; class Circle : public Shape { int radius_; public: explicit Circle(int radius) noexcept : radius_{radius} {} auto area() const noexcept -> double override { return std::numbers::pi * radius_ * radius_; } };
これらの実装をインターフェースに対して汎用的に使えるようにすれば、次のように書けます。
auto printArea(const Shape& shape) -> void { std::println("Area is {:.2f}", shape.area()); }
テンプレートによる多態性
継承は具体的な型が共通の基底クラスを持つときにうまく機能します。
しかし、例えば
std::string や組み込み型(int など)では変更できません。このような場合でも同じインターフェースを提供していれば、テンプレートを使って代替できます。
auto printArea(const auto& shape) -> void { std::println("Area is {:.2f}", shape.area()); }
これは
area() を引数なしで呼び出せて double を返す任意の型に対して機能します。ただし、テンプレートには主に次の二つの欠点があります。
-
共有ランタイム基底がない – 各インスタンス化は別個なので、
とSquare
を同じコンテナに混在させることができません。Circleauto shapes = std::vector< ??? >{ &square, &circle }; -
テンプレートの増殖 – 呼び出し側もテンプレートである必要があり、コンパイル時間が長くなりバイナリサイズが大きくなる可能性があります。
std::any
の導入
std::anySquare と Circle が固定された型で共通の基底クラスを持たず、変更できないと仮定します。依然として単一の共通インターフェースで扱いたい場合、ラッパーを導入する方法があります。
class SquareWrapper : public Shape { Square square_; public: explicit SquareWrapper(Square square) noexcept : square_{std::move(square)} {} auto area() const noexcept -> double override { return square_.area(); } }; class CircleWrapper : public Shape { Circle circle_; public: explicit CircleWrapper(Circle circle) noexcept : circle_{std::move(circle)} {} auto area() const noexcept -> double override { return circle_.area(); } };
こうすれば
Shape のインスタンスを直接扱えます。
auto printAreas(const std::vector<std::unique_ptr<Shape>>& shapes) -> void { for (const auto& shape : shapes) std::println("Area is {:.2f}", shape->area()); } int main() -> int { std::vector<std::unique_ptr<Shape>> shapes; shapes.emplace_back(std::make_unique<SquareWrapper>(Square{2})); shapes.emplace_back(std::make_unique<CircleWrapper>(Circle{1})); printAreas(shapes); }
この手法は動作しますが、明らかな欠点があります:各具体型ごとに別々のラッパー型を用意する必要があります。テンプレートを使えば多くの作業を簡略化できます。
template <typename T> class ShapeWrapper : public Shape { T shape_; public: explicit ShapeWrapper(T shape) noexcept : shape_{std::move(shape)} {} auto area() const noexcept -> double override { return shape_.area(); } };
「型消去」イディオム
残るのは、上記の仕組みをさらに一つのクラスに隠すことで、呼び出し側がカスタムインターフェースやテンプレートに対処する必要がなくなることです。典型的な実装(常にヒープ割り当て)は次のようになります。
class AnyShape { // インターフェース class Shape { public: virtual ~Shape() = default; virtual auto area() const noexcept -> double = 0; }; // ラッパー template <typename T> class ShapeWrapper : public Shape { T shape_; public: explicit ShapeWrapper(T shape) noexcept : shape_{std::move(shape)} {} auto area() const noexcept -> double override { return shape_.area(); } }; std::unique_ptr<Shape> shape_; public: template <typename T> explicit AnyShape(T&& shape) : shape_{std::make_unique<ShapeWrapper<T>>(std::forward<T>(shape))} {} auto area() const noexcept -> double { return shape_->area(); } };
使用例は以前と同じです。
auto printAreas(const std::vector<AnyShape>& shapes) -> void { for (const auto& shape : shapes) std::println("Area is {:.2f}", shape.area()); } int main() -> int { std::vector<AnyShape> shapes; shapes.emplace_back(Square{2}); shapes.emplace_back(Circle{1}); printAreas(shapes); }
汎用的な std::any
std::anyConcept と Model は標準名です:前者は 型消去コンセプト(OO スタイルのインターフェースで、C++20 の concept とは無関係)を指し、後者はそのインターフェースを実装するテンプレートラッパー(モデル)です。
以下は
std::any を模した最小限の Any クラスです。
#include <memory> class Any { // インターフェース class Concept { public: virtual ~Concept() = default; virtual auto f() const noexcept -> double = 0; }; // モデル template <typename T> class Model : public Concept { T obj_; public: explicit Model(T obj) noexcept : obj_{std::move(obj)} {} auto f() const noexcept -> double override { return obj_.f(); } }; std::unique_ptr<Concept> obj_; public: template <typename T> explicit Any(T&& obj) : obj_{std::make_unique<Model<T>>(std::forward<T>(obj))} {} auto f() const noexcept -> double { return obj_->f(); } };
これで完了です!
Any クラスは std::any の簡略版であり、型消去の核となるアイデアを示しています。実際には STL でも同様の手法が使われています(例:std::function)。
— David Álvarez Rosa