```wikitext

コンセプトの概要

編集

コンセプトとは何か

編集

コンセプト (concept) は、C++20から導入された新しい言語機能です。コンセプトは、型が満たすべき一連の要件を定義するもので、これによりジェネリックプログラミングにおいてテンプレート引数として渡される型がその要件を満たしているかどうかをコンパイル時に検査できるようになります。

コンセプトの役割と目的

編集

コンセプトの主な役割は、ジェネリックコードの安全性と表現力を高めることです。安全性の向上は、不適切な型がテンプレート引数として渡された場合にコンパイルエラーを発生させることで実現されます。表現力の向上は、コンセプトによってコードの意図がより明確に表現できるためです。

コンセプトの目的は次の3点に集約されます。

  1. コンパイル時の型検査を可能にする。
  2. ジェネリックコードの意図を明確に表現する。
  3. 適切なエラーメッセージを出力する。

コンセプトによるジェネリックプログラミングの利点

編集

コンセプトを使うことで、以下のような利点が得られます。

安全性の向上
コンパイル時に型の要件を検査できるため、実行時エラーを減少させることができます。
表現力の向上
コンセプトによって要件を明示的に表現することで、コードの意図がより明確になります。
エラーメッセージの改善
コンセプトに基づいたエラーメッセージがコンパイラによって生成され、エラーの原因を特定しやすくなります。
コード再利用性の向上
要件をコンセプトとして抽出することで、異なる場所で再利用が可能になります。
拡張性の向上
新しいコンセプトを定義して要件を追加することで、ジェネリックコードの柔軟性を高めることができます。

コンセプトの種類

編集

C++20には、多くの標準コンセプトが用意されています。以下に主なコンセプトの分類を示します。

簡単なコンセプト (EqualityComparable、LessThanComparable など)

編集

簡単なコンセプトは、型が等値比較や大小比較をサポートするかを示します。代表的なコンセプトは次のとおりです。

EqualityComparable
==!= 演算子をサポートする型。
EqualityComparableWith
2つの型の間で ==!= 演算子がサポートされる。
LessThanComparable
< 演算子をサポートする型。
LessThanComparableWith
2つの型の間で < 演算子がサポートされる。

これらは、ソート済みコンテナやアルゴリズムの実装で用いられます。

コンテナ向けのコンセプト (Sequence、AssociativeContainer など)

編集

コンテナ向けのコンセプトは、型がコンテナの要件を満たすかどうかを示します。代表例は次のとおりです。

Sequence
順序付きコンテナ (例: vectordeque) の要件を満たす型。
RandomAccessRange
ランダムアクセス可能な範囲をサポートする型。
AssociativeContainer
値に対する関数射影を提供するコンテナ (例: mapset) の要件を満たす型。
UnorderedAssociativeContainer
ハッシュを使用するコンテナの要件を満たす型。

これらは、関数テンプレートのパラメータとしてコンテナを使用する際に適用されます。

イテレータ向けのコンセプト (InputIterator、BidirectionalIterator など)

編集

イテレータ向けのコンセプトは、特定のイテレータ型が満たすべき要件を定義します。代表的なものは次のとおりです。

InputIterator
単方向イテレータの要件を満たす型。
OutputIterator
出力イテレータの要件を満たす型。
ForwardIterator
前方向イテレータの要件を満たす型。
BidirectionalIterator
双方向イテレータの要件を満たす型。
RandomAccessIterator
ランダムアクセスイテレータの要件を満たす型。

これらは、標準アルゴリズム (例: std::copystd::sort) で広く使用されています。

その他のコンセプト (Callable、Predicate など)

編集

その他の汎用コンセプトも存在します。

Callable
関数呼び出し可能な型。
Predicate
真偽値を返す関数オブジェクトとして使用される型。
Range
範囲をサポートする型。
Semiregular
コピー構築可能かつデフォルト構築可能な型。
Regular
値の等値比較ができ、コピー代入可能な型。

これらのコンセプトはアルゴリズムやユーティリティ関数で使用されます。例えば、std::all_ofstd::find_if では Predicate コンセプトが使われます。

コンセプトの定義方法

編集

概念的制約 (concept constraints) の記述

編集

コンセプトは、型が満たすべき制約を表現します。制約は次のように記述されます。

template<typename T>
concept concept_name = /* 制約条件 */;

たとえば、EqualityComparable は次のように定義されます。

template<class T>
concept EqualityComparable = requires(T a, T b) {
    { a == b } -> std::convertible_to<bool>; // a == b の結果がブール値に変換可能
    { a != b } -> std::convertible_to<bool>; // a != b の結果もブール値に変換可能
};

この定義は型 T==!= 演算子をサポートすることを表しています。

コンセプト定義の構文

編集

コンセプトは以下の構文で定義されます。

concept concept_name = constraint_expression;
  • concept_name: コンセプトの名前
  • constraint_expression: 型が満たすべき制約条件を記述する論理式

制約条件の例:

型トレイト
std::is_integral<T>, std::is_default_constructible<T> など
簡単な要件
sizeof(T) <= 16, std::has_virtual_destructor<T> など
合成要件
EqualityComparable<T>, std::derived_from<T, Base> など
演算子要件
a == b, a != b, a < b など
メンバ関数要件
a.empty(), a.size(), T::value_type など

コンパウンドコンセプト (compound concepts)

編集

複数のコンセプトを組み合わせて新しいコンセプトを作成することもできます。例として次に、複数のコンセプトを組み合わせる方法を示します。

template<typename T>
concept Hashable = requires(T a) {
    { std::hash<T>{}(a) } -> std::convertible_to<std::size_t>;
};

template<typename T>
concept CustomConcept = EqualityComparable<T> && Hashable<T>;

この例では、CustomConcept という新しいコンセプトが EqualityComparableHashable の両方を満たす型として定義されています。このように、コンパウンドコンセプトはジェネリックコードの柔軟性を高めるために使用されます。

コンセプトの使用方法

編集

テンプレート宣言におけるコンセプトの使用

編集

テンプレート関数やクラスの定義において、コンセプトを使用することで、型パラメータの制約を明示的に指定できます。これにより、意図しない型の使用を防ぐことができます。

以下に、コンセプトを使用したテンプレート関数の例を示します。

template<EqualityComparable T>
bool are_equal(const T& a, const T& b) {
    return a == b;
}

この例では、テンプレート引数 TEqualityComparable コンセプトを満たす型であることを要求しています。この関数は、== 演算子が使用できる型のみ受け入れます。

requires 式の使用

編集

コンセプトは requires 式を用いて関数内で直接使用することもできます。これにより、関数内で特定の条件を満たしているかどうかを柔軟にチェックできます。

template<typename T>
requires std::integral<T>
T add(T a, T b) {
    return a + b;
}

この例では、Tstd::integral コンセプトを満たす場合にのみ、add 関数が使用できるようになっています。requires キーワードを使用することで、テンプレート宣言に制約を簡潔に追加できます。

概念ベースのオーバーロード

編集

コンセプトを使用すると、関数のオーバーロードを簡単に定義できます。たとえば、異なるコンセプトを持つ型に対して異なる処理を提供することが可能です。

template<typename T>
requires std::integral<T>
void print_info(const T& value) {
    std::cout << "Integral value: " << value << '\n';
}

template<typename T>
requires std::floating_point<T>
void print_info(const T& value) {
    std::cout << "Floating-point value: " << value << '\n';
}

この例では、print_info 関数は std::integral 型と std::floating_point 型で異なる出力を行います。コンセプトに基づいて関数のオーバーロードを選択できるため、コードの意図がより明確になります。

まとめ

C++20 のコンセプトは、型パラメータに制約を与えることでジェネリックプログラミングをより安全かつ表現力豊かにします。コンセプトを用いることで、意図しない型の使用を防ぎ、コードの可読性と保守性を向上させることができます。また、エラーメッセージも改善され、開発者は問題の原因を特定しやすくなります。

コンセプトは今後の C++ プログラミングにおいて、ますます重要な役割を果たしていくでしょう。これにより、ジェネリックコードをより安全で強力なものにするための道が開かれます。