コンテンツにスキップ

最終更新: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 構文

構文:

template <template-parameter-list>
concept concept-name = constraint-expression;

実際の例と対応させると、Addable が concept-name、constraint-expression がrequires(T a, T b) { a + b; }です。

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

使用方法:

#include <concepts>

この concept は複数の場所に置けます。以下の通りです:

  • typename の位置
  • requires の後
  • auto の前

1.1 typename の置き換え

テンプレート引数を制約し、typename を置き換えます。

// template <typename T>  typename->Addable
template <Addable T>
T Add(T a, T b) {
  return a + b;
}

1.2 requires キーワード

関数テンプレートの中で requires キーワードを使います。テンプレート T が加算可能かどうかを判定でき、テンプレートが加算を処理できる場合に true を返します。

requires はテンプレートの中にも置けますし、関数の後にも置けますが、クラスの後には置けません。また、2つの書き方があります:

  • requires 条件

例えば:

template <typename T>
  requires Addable<T>
T Add(T a, T b) { ... }
  • requires 式

例えば:

template <typename T>
concept Addable = requires(T a, T b) { a + b; };

1.2.1 テンプレートの中に置く

関数:

template <typename T>
  requires Addable<T>
T Add(T a, T b) {
  return a + b;
}

クラス:

template <typename T>
    requires Addable<T> 
class Bar {
 public:
  T Add(T a, T b) { return a + b; }
};

1.2.2 関数の末尾の Requires 節

関数:

template <typename T>
T Add(T a, T b)
  requires Addable<T>
{
  return a + b;
}

クラスではこの書き方がサポートされず、エラーになります:error: expected unqualified-id before 'requires' 28 | requires Addable

template <typename T>
class Bar requires Addable<T> 
{
 public:
  T Add(T a, T b) { return a + b; }
};

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 と一緒に使えます。例えば:

auto add(auto x, auto y) {
  return x + y;
}

これは次のように書き換えられます:

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です。

https://en.cppreference.com/w/cpp/compiler_support

3.まとめ

C++20 で concept が提供されて以来、テンプレートの制約チェックのために enable_if/SFINAE の仕組みや関数オーバーロードを使う必要はなくなりました。concept がこの操作をやってくれます。より明清晰で強力なテンプレート引数の制約機構を提供し、テンプレートコードをより書きやすく、理解しやすく、保守しやすくします。コンパイル時の型チェックにより、コードの堅牢性と可読性の向上にも役立ちます。

関連トピック

コメント