C++ コンセプト(concepts)の型制約のテスト

C++20から導入されたコンセプト(concepts)は、テンプレート型のパラメータの制約を実現する、型制約の言語機能です。
コンセプトを使うと、従来のSFINAEなどのテクニックと比べて、簡潔に型の制約を記述できるようになるほか、制約違反時のエラーメッセージを格段に読みやすくできます。
ただコンセプトによって、型の制約をより複雑に書けるようになる側面があります。そこで需要が増えるのが、型制約のテストです。複雑なコンセプトを書けばプログラミング中に確認を行いたくなりますし、また例えばライブラリなどの汎用的なAPIでは型制約は重要な品質保証の対象になります。

コンセプトのテストの記述

コンセプトはコンパイル時に評価され、それ単体では制約を満たせばbool型でtrueを、制約に違反すればfalseを返す挙動をとります。そのためコンセプトのテストは、コンパイル時評価手段のstatic_assertを使って簡単に実現できます。
サンプルコードを示します。

#include <concepts>

template <class T, class U>
concept Comparable = requires (T t, U u)
{
  {t == u} -> std::convertible_to<bool>;
};

class Dummy
{//Comparableの制約を満たさないダミー
};

int main()
{
  //コンセプトのテスト
  static_assert(Comparable<int, double>);
  static_assert(Comparable<int, bool>);
  static_assert(not Comparable<int, Dummy>);//等価比較不能
}

以下がテスト対象のコンセプト記述です。等価比較演算できることを制約で保証します。このコンセプトが適用されたテンプレートのパラメータが制約違反すると、コンパイル不能になります。

template <class T, class U>
concept Comparable = requires (T t, U u)
{
  {t == u} -> std::convertible_to<bool>;
};

以下がコンセプトのテストの記述になります。

  static_assert(Comparable<int, double>);
  static_assert(Comparable<int, bool>);
  static_assert(not Comparable<int, Dummy>);//等価比較不能

static_assertでコンパイル時テストとして処理します。コンセプトを満たすことを確認するテストはコンセプトをそのまま、コンセプトに違反することを確認するテストはnot演算子を付与して記述します。
今回のコンセプトはこのテストに合格するため、コンパイルに成功します。

コンセプトのテストに失敗する場合

仮に、Dummyで次のように等価比較をオーバーロードして、Comparableが成立するように改変します。

class Dummy
{
public:
	bool operator == (const bool obj) const
	{
		return true == obj;
	}
};

これを前述のコンパイル時テストで実行すると、コンパイルが次のエラーで失敗するようになります。

sample.cpp:22:17: error: static assertion failed
   22 |   static_assert(not Comparable<int, Dummy>);
      |                 ^~~~~~~~~~~~~~~~~~~~~~~~~~