【C++】std::conceptsでテンプレートの型に対する制約を指定する【標準ライブラリ】
std::conceptsとは
C++20で導入された「概念 (Concepts)」は、テンプレートの型に対する制約を指定するための仕組みです。従来のSFINAE (Substitution Failure Is Not An Error) や enable_if
を使った方法よりも簡潔で可読性の高いコードを記述できます。
基本的な使い方
概念を利用することで、テンプレート引数の制約を明示的に指定できます。例えば、以下のように書くことで整数型のみに適用される関数を作成できます。
#include <concepts>
#include <iostream>
void print(std::integral auto value) {
std::cout << "整数値: " << value << std::endl;
}
int main() {
print(42); // OK
// print(3.14); // コンパイルエラー
}
組み込みの概念(std::concepts)
標準ライブラリ<std::concepts> には、よく使われる型制約のための組み込み概念が提供されています。
std::integral
– 整数型std::floating_point
– 浮動小数点型std::signed_integral
– 符号付き整数型std::unsigned_integral
– 符号なし整数型std::same_as
– 型Tと型Uが同じかstd::derived_from
– TがUの派生クラスかstd::convertible_to
– TがUに変換可能か
カスタム概念の定義
独自の概念を定義することで、より柔軟な型制約を作成できます。
#include <concepts>
#include <iostream>
template <typename T>
concept Addable = requires(T a, T b) {
{ a + b } -> std::same_as;
};
void addAndPrint(Addable auto x, Addable auto y) {
std::cout << "合計: " << (x + y) << std::endl;
}
int main() {
addAndPrint(3, 4); // OK
// addAndPrint("a", "b"); // コンパイルエラー
}
概念の組み合わせ
複数の概念を組み合わせることも可能です。
template <typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;
void process(Numeric auto value) {
std::cout << "処理中: " << value << std::endl;
}
int main() {
process(42); // OK
process(3.14); // OK
// process("hello"); // コンパイルエラー
}
使用できる構文
概念は以下のように指定できます。
// 1. 直接autoの制約として使用
void func(std::integral auto value);
// 2. テンプレート引数に制約を適用
template <std::integral T>
void func(T value);
// 3. requires節を使用
template <typename T>
requires std::integral<T>
void func(T value);
具体例
概念を利用した汎用的なコード例をいくつか示します。
比較可能な型
template <typename T>
concept Comparable = requires(T a, T b) {
{ a == b } -> std::convertible_to;
};
void compare(Comparable auto a, Comparable auto b) {
std::cout << "比較結果: " << (a == b) << std::endl;
}
コンテナ要素が整数型であることを確認
#include <vector>
#include <concepts>
template <typename T>
concept IntegerContainer = requires(T t) {
typename T::value_type;
requires std::integral<typename T::value_type>;
};
void processContainer(IntegerContainer auto& container) {
for (auto& elem : container) {
std::cout << elem << " ";
}
std::cout << std::endl;
}
int main() {
std::vector<int> v = {1, 2, 3, 4};
processContainer(v); // OK
}
メリットと注意点
メリット
- コードの可読性が向上する
- テンプレートのエラーメッセージがわかりやすくなる
- 意図しない型の誤用を防げる
注意点
- 概念を過度に細かくしすぎると可読性が下がる
- C++20以降でしか使用できない