最終更新:2026-10-12
C++20 Concepts コンセプト¶
C++20 は concept(コンセプト)を導入しました。これはテンプレート引数(コンパイル時評価)に対する一連の制約です。クラステンプレートや関数テンプレートで使って、関数のオーバーロードと特殊化を制御できます。利点には以下が含まれます:
- テンプレート引数に型の制約を強制できる
- コードの可読性が向上する(長い SFINAE コードを置き換える)
- より親切なエラーメッセージを提供する
- 使用できる型を制限することで、意図しないテンプレートのインスタンス化を防ぐ
比較的単純な加法の例で見てみましょう。
#include <iostream>
struct Foo {};
template <typename T>
T Add(T a, T b) {
return a + b;
}
int main() {
std::cout << Add(1, 2) << std::endl;
Foo f1, f2;
std::cout << Add(f1, f2) << std::endl;
return 0;
}
Foo は加法をサポートしておらず、同時に直接std::cout <<もできないため、コンパイル時にoperator<<とoperator+を含む大量のエラーが出ます。しかしこれは私たちが期待するエラーメッセージではありません。期待するのは、コンパイラが最も直感的なエラー、すなわち「この構造体は加算できるか」を教えてくれることです。
add.cc: In function 'int main()':
add.cc:13:13: error: no match for 'operator<<' (operand types are 'std::ostream' {aka 'std::basic_ostream<char>'} and 'Foo')
13 | std::cout << Add(f1, f2) << std::endl;
| ~~~~~~~~~ ^~ ~~~~~~~~~~~
| | |
| | Foo
| std::ostream {aka std::basic_ostream<char>}
In file included from /usr/local/Cellar/gcc/13.2.0/include/c++/13/iostream:41,
from add.cc:1:
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:110:7: note: candidate: 'std::basic_ostream<_CharT, _Traits>::__ostream_type& std::basic_ostream<_CharT, _Traits>::operator<<(__ostream_type& (*)(__ostream_type&)) [with _CharT = char; _Traits = std::char_traits<char>; __ostream_type = std::basic_ostream<char>]'
110 | operator<<(__ostream_type& (*__pf)(__ostream_type&))
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:801:5: note: template argument deduction/substitution failed:
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream: In substitution of 'template<class _Ostream, class _Tp> _Ostream&& std::operator<<(_Ostream&&, const _Tp&) [with _Ostream = std::basic_ostream<char>&; _Tp = Foo]':
add.cc:13:26: required from here
/usr/local/Cellar/gcc/13.2.0/include/c++/13/ostream:801:5: error: no type named 'type' in 'struct std::enable_if<false, void>'
add.cc: In instantiation of 'T Add(T, T) [with T = Foo]':
add.cc:13:19: required from here
add.cc:7:12: error: no match for 'operator+' (operand types are 'Foo' and 'Foo')
7 | return a + b;
| ~~^~~
concept を使って実装すると:
template <typename T>
concept Addable = requires(T a, T b) { a + b; };
template <typename T>
requires Addable<T>
T Add(T a, T b) {
return a + b;
}
私たちが関心のあるコンパイルエラーが得られます:
add_concept.cc: In function 'int main()':
add_concept.cc:17:19: error: no matching function for call to 'Add(Foo&, Foo&)'
17 | std::cout << Add(f1, f2) << std::endl;
| ~~~^~~~~~~~
add_concept.cc:10:3: note: candidate: 'template<class T> requires Addable<T> T Add(T, T)'
10 | T Add(T a, T b) {
| ^~~
add_concept.cc:10:3: note: template argument deduction/substitution failed:
add_concept.cc:10:3: note: constraints not satisfied
add_concept.cc: In substitution of 'template<class T> requires Addable<T> T Add(T, T) [with T = Foo]':
add_concept.cc:17:19: required from here
add_concept.cc:6:9: required for the satisfaction of 'Addable<T>' [with T = Foo]
add_concept.cc:6:19: in requirements with 'T a', 'T b' [with T = Foo]
add_concept.cc:6:42: note: the required expression '(a + b)' is invalid
6 | concept Addable = requires(T a, T b) { a + b; };
| ~~^~~
以下、上の例について concept の構文を深く学んでいきましょう。
1.concept 構文¶
構文:
実際の例と対応させると、Addable が concept-name、constraint-expression がrequires(T a, T b) { a + b; }です。
使用方法:
この concept は複数の場所に置けます。以下の通りです:
- typename の位置
- requires の後
- auto の前
1.1 typename の置き換え¶
テンプレート引数を制約し、typename を置き換えます。
1.2 requires キーワード¶
関数テンプレートの中で requires キーワードを使います。テンプレート T が加算可能かどうかを判定でき、テンプレートが加算を処理できる場合に true を返します。
requires はテンプレートの中にも置けますし、関数の後にも置けますが、クラスの後には置けません。また、2つの書き方があります:
- requires 条件
例えば:
- requires 式
例えば:
1.2.1 テンプレートの中に置く¶
関数:
クラス:
1.2.2 関数の末尾の Requires 節¶
関数:
クラスではこの書き方がサポートされず、エラーになります:error: expected unqualified-id before 'requires' 28 | requires Addable
1.2.3 requires の基本例¶
データベースの型を例にとります。データベースには異なる型があり、null、binary、number などに分類できます。渡された型に対して print 操作を実行したいとしましょう。そこで以下の例が生まれます:
#include <concepts>
class NumberType {};
class BaseBinaryType {};
class NullType {};
class FloatingPointType : public NumberType {};
class IntegerType : public NumberType {};
class BinaryType: public BaseBinaryType {};
template <typename T>
requires std::is_base_of_v<NumberType, T> || std::is_base_of_v<BaseBinaryType, T>
void PrintValue(T v) {}
int main() {
PrintValue(FloatingPointType{});
PrintValue(NullType{});
return 0;
}
requires では||が使えます。上の例で NullType が現れており、これはrequiresを満たさないため、コンパイルすると次のようになります:
concept_requires.cc:16:13: error: no matching function for call to 'PrintValue(NullType)'
16 | PrintValue(NullType{});
| ~~~~~~~~~~^~~~~~~~~~~~
concept_requires.cc:12:6: note: candidate: 'template<class T> requires (is_base_of_v<NumberType, T>) || (is_base_of_v<BaseBinaryType, T>) void PrintValue(T)'
12 | void PrintValue(T v) {}
| ^~~~~~~~~~
concept_requires.cc:12:6: note: template argument deduction/substitution failed:
concept_requires.cc:12:6: note: constraints not satisfied
concept_requires.cc: In substitution of 'template<class T> requires (is_base_of_v<NumberType, T>) || (is_base_of_v<BaseBinaryType, T>) void PrintValue(T) [with T = NullType]':
concept_requires.cc:16:13: required from here
concept_requires.cc:12:6: required by the constraints of 'template<class T> requires (is_base_of_v<NumberType, T>) || (is_base_of_v<BaseBinaryType, T>) void PrintValue(T)'
concept_requires.cc:11:45: note: no operand of the disjunction is satisfied
11 | requires std::is_base_of_v<NumberType, T> || std::is_base_of_v<BaseBinaryType, T>
| ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1.3 concept と auto¶
auto が現れるとき、concept と一緒に使えます。例えば:
これは次のように書き換えられます:
template <typename T>
concept Addable = requires(T a, T b) { a + b; };
auto add2(Addable auto x, Addable auto y) {
return x + y;
}
コンパイルすると次のようになります:
concept_auto.cc:17:20: error: no matching function for call to 'add2(Foo&, Foo&)'
17 | std::cout << add2(f1, f2) << std::endl;
| ~~~~^~~~~~~~
concept_auto.cc:10:6: note: candidate: 'template<class auto:18, class auto:19> requires (Addable<auto:18>) && (Addable<auto:19>) auto add2(auto:18, auto:19)'
10 | auto add2(Addable auto x, Addable auto y) {
| ^~~~
concept_auto.cc:10:6: note: template argument deduction/substitution failed:
concept_auto.cc:10:6: note: constraints not satisfied
concept_auto.cc: In substitution of 'template<class auto:18, class auto:19> requires (Addable<auto:18>) && (Addable<auto:19>) auto add2(auto:18, auto:19) [with auto:18 = Foo; auto:19 = Foo]':
concept_auto.cc:17:20: required from here
concept_auto.cc:8:9: required for the satisfaction of 'Addable<auto:18>' [with auto:18 = Foo]
concept_auto.cc:8:19: in requirements with 'T a', 'T b' [with T = Foo]
concept_auto.cc:8:42: note: the required expression '(a + b)' is invalid
8 | concept Addable = requires(T a, T b) { a + b; };
2.コンパイラサポート¶
GCC(10.0+)、Clang(10.0+)が必要で、コンパイルオプションは-std=c++20/-std=c++2aです。
3.まとめ¶
C++20 で concept が提供されて以来、テンプレートの制約チェックのために enable_if/SFINAE の仕組みや関数オーバーロードを使う必要はなくなりました。concept がこの操作をやってくれます。より明清晰で強力なテンプレート引数の制約機構を提供し、テンプレートコードをより書きやすく、理解しやすく、保守しやすくします。コンパイル時の型チェックにより、コードの堅牢性と可読性の向上にも役立ちます。
関連トピック¶
- C++20 コルーチン入門 | C++那些事
- C++20 channel 実践 | C++那些事
- C++20 constexpr とカンマ式 | C++那些事
- C++20 バイトオーダー endian | C++那些事
- C++20 Lambda 式(前編) | C++那些事
- C++20 モジュール Modules | C++那些事
- C++20 Ranges ライブラリ | C++那些事
- C++20 std::span の設計と落とし穴 | C++那些事
- C++20 新機能ガイド | C++那些事
- C++20 [[likely]] と [[unlikely]] | C++那些事