コンセプトで型をチェック

コンセプトは、型が満たされているかどうかをコンパイル時にチェックする強力で洗練されたツールです。 static_assert に感謝 、概念をスタンドアロン機能として使用できます:static_assert(Concept<T>) .

C++ のクラスでよく質問があります。データ型が移動可能であることを確認するにはどうすればよいですか?ビッグ 6 間の依存関係を調べることも、ビッグ 6 の概念を定義して使用することもできます。私は前回の投稿「概念に基づいて型をチェックする - 動機」で答えの最初の部分を提示し、ビッグ 6 間の非常に洗練された依存関係について説明しました。念のため、移動のセマンティクスを含むビッグ 6 を次に示します。

  • デフォルトのコンストラクタ: X()
  • コピー コンストラクタ:X(const X&)
  • 割り当てをコピー:operator = (const X&)
  • 移動コンストラクタ:X(X&&)
  • 移動の割り当て: operator = (X&&)
  • デストラクタ: ~(X)

今日は、ビッグ シックスの概念を定義して使用したいと思います。

その前に、短い免責事項があります:C++20 は既にコンセプト std::semiregular をサポートしています そして std::regular .

std::semiregularstd::regular

セミレギュラー タイプは Big Six をサポートする必要があり、スワップ可能でなければなりません:

  • デフォルトのコンストラクタ: X()
  • コピー コンストラクタ:X(const X&)
  • 割り当てをコピー:operator = (const X&)
  • 移動コンストラクタ:X(X&&)
  • 移動の割り当て: operator = (X&&)
  • デストラクタ: ~(X)
  • スワップ可能: swap(X&, X&)

さらに、 std::regular タイプ X が必要です コンセプト std::semiregular をサポートしていること 同等であり、比較可能です。

  • デフォルトのコンストラクタ: X()
  • コピー コンストラクタ:X(const X&)
  • 割り当てをコピー:operator = (const X&)
  • 移動コンストラクタ:X(X&&)
  • 移動の割り当て: operator = (X&&)
  • デストラクタ: ~(X)
  • スワップ可能: swap(X&, Y&)
  • 等価比較: bool operator == (const X&, const X&)

とはいえ、BigSix という概念を定義する理由は本質的にありません。概念 std::semiregular, を使用するだけです スワップ可能なプロパティを無料で取得できるためです。これは std::swap の C++11 実装です。 :

template <typename T>
void swap(T& a, T& b) noexcept {
 T tmp(std::move(a)); // move constructor
 a = std::move(b); // move assignment
 b = std::move(tmp); // move assignment
}

swap(a, b) を呼び出すと 、コンパイラは移動セマンティクスを引数 a に適用します と b .結果として、概念 BigSix をサポートする型はスワップ可能もサポートするため、概念 std::semiregular をサポートします .

それでは、BigSix のコンセプトを実装しましょう。

コンセプト BigSix

型特性関数のおかげで、BigSix の概念を簡単に実装できます。最初のステップでは、型特性 isBigSix を定義します 2 番目のステップでは、それを直接使用してコンセプト BigSix を定義します。 .

// bigSixConcept.cpp

#include <algorithm>
#include <iostream>
#include <type_traits>

template<typename T>
struct isBigSix: std::integral_constant<bool,
 std::is_default_constructible<T>::value &&
 std::is_copy_constructible<T>::value &&
 std::is_copy_assignable<T>::value &&
 std::is_move_constructible<T>::value &&
 std::is_move_assignable<T>::value &&
 std::is_destructible<T>::value>{};


template<typename T>
concept BigSix = isBigSix<T>::value;

template <BigSix T> // (1)
void swap(T& a, T& b) noexcept {
 T tmp(std::move(a));
 a = std::move(b);
 b = std::move(tmp);
}

struct MyData{ // (2)
 MyData() = default;
 MyData(const MyData& ) = default;
 MyData& operator=(const MyData& m) = default;

};

int main(){

 std::cout << '\n';

 MyData a, b;
 swap(a, b); // (3)

 static_assert(BigSix<MyData>, "BigSix not supported"); // (4)

 std::cout << '\n';

}

さて、私の関数 swap 型パラメーター T が BigSix の概念をサポートしている必要があります (1 行目)。 3 行目で、関数 swap を呼び出します。 タイプ MyData の引数付き .さらに、4 行目で MyData かどうかを明示的にチェックします。 コンセプト BigSix をサポート . MyData (2 行目) にはデフォルトのコンストラクターがあり、コピー セマンティクスをサポートしています。プログラムはコンパイルして実行できます。

これは MyData ということですか? コンセプト BigSix をサポート したがって、関数 swap 内に移動されます ?はい、MyData コンセプト BigSix をサポートしていますが、サポートしていません。MyData 関数 swap 内で移動されません .コピー セマンティクスは、ムーブ セマンティクスのフォールバックとして機能します。

これは少し変更されたプログラムです。

// bigSixConceptComments.cpp

#include <algorithm>
#include <iostream>
#include <type_traits>

template<typename T>
struct isBigSix: std::integral_constant<bool,
 std::is_default_constructible<T>::value &&
 std::is_copy_constructible<T>::value &&
 std::is_copy_assignable<T>::value &&
 std::is_move_constructible<T>::value &&
 std::is_move_assignable<T>::value &&
 std::is_destructible<T>::value>{};


template<typename T>
concept BigSix = isBigSix<T>::value;

template <BigSix T> 
void swap(T& a, T& b) noexcept {
 T tmp(std::move(a));
 a = std::move(b);
 b = std::move(tmp);
}

struct MyData{ 
 MyData() = default;
 MyData(const MyData& ) {
 std::cout << "copy constructor\n";
 }
 MyData& operator=(const MyData& m) {
 std::cout << "copy assignment operator\n";
 return *this;
 }

};

int main(){

 std::cout << '\n';

 MyData a, b;
 swap(a, b); 
 
 static_assert(BigSix<MyData>, "BigSix not supported"); 

 std::cout << '\n';

}

MyData のコピーコンストラクタとコピー代入演算子にコメントを追加しました .プログラムを実行すると、両方の特別なメンバー関数が使用されていることがわかります:

ちなみに、この観察結果は cppreference.com で既に文書化されています。たとえば、型特性 std::is_move_constructible に関するメモには次のように記載されています。 "

さて、振り出しに戻ります。型が BigSix をサポートしているかどうかは判断できますが、型が本当に移動されているかどうかは判断できません。コピー セマンティクスがムーブ セマンティクスのフォールバックとして使用されているのではなく、型がムーブ セマンティクスをサポートしているかどうかを知りたい場合は、私の以前の投稿の依存関係テーブルを調べる必要があります。>

次は?

次の投稿では、範囲についての話を続けたいと思います。さらに、範囲は C++23 で多くの改善が得られます。