C++20:概念、詳細

前回の投稿 C++20:Two Extremes and the Rescue with Concepts で、コンセプトの最初の動機を示しました。概念は、テンプレート パラメーターにセマンティックな制約を課します。今日は、概念のさまざまな使用例をコンパクトな形式で紹介します。

詳細

心に留めておいてください:概念の利点は何ですか?

  • テンプレートの要件はインターフェースの一部です。
  • 関数のオーバーロードまたはクラス テンプレートの特殊化は、概念に基づくことができます。
  • コンパイラがテンプレート パラメータの要件と実際のテンプレート引数を比較するため、エラー メッセージが改善されます
  • 定義済みの概念を使用するか、独自の概念を定義できます。
  • auto と概念の使用法は統一されています。 auto の代わりにコンセプトを使用できます。
  • 関数宣言が概念を使用する場合、それは自動的に関数テンプレートになります。したがって、関数テンプレートの作成は、関数を作成するのと同じくらい簡単です。

この投稿は最初の 3 つのポイントについてです。概念のさまざまな使用法を示しましょう:

3 つの方法

Sortable という概念を使用する方法は 3 つあります。簡単にするために、関数テンプレートの宣言のみを示します。

句が必要

template<typename Cont>
 requires Sortable<Cont>
void sort(Cont& container);

末尾に句が必要

template<typename Cont>
void sort(Cont& container) requires Sortable<Cont>;

制約付きテンプレート パラメータ

template<Sortable Cont>
void sort(Cont& container)

この場合、アルゴリズムの並べ替えでは、コンテナーが並べ替え可能である必要があります。 Sortable は、定数式と述語でなければなりません。

クラス

オブジェクトのみを受け入れるクラス テンプレートを定義できます。

template<Object T>
class MyVector{};

MyVector<int> v1; // OK
MyVector<int&> v2; // ERROR: int& does not satisfy the constraint Object

コンパイラは、参照がオブジェクトではないと不平を言います。オブジェクトとは何か、と疑問に思うかもしれません。型特性関数 std::is_object の可能な実装は答えを与えます:

template< class T>
struct is_object : std::integral_constant<bool,
 std::is_scalar<T>::value ||
 std::is_array<T>::value ||
 std::is_union<T>::value ||
 std::is_class<T>::value> {};

オブジェクトは、スカラー、配列、共用体、またはクラスのいずれかです。

メンバー関数

template<Object T>
class MyVector{
 ... 
 void push_back(const T& e) requires Copyable<T>{}
 ...
};

この場合、メンバー関数は、テンプレート パラメーター T がコピー可能である必要があります。

バリアディック テンプレート

 // allAnyNone.cpp

#include <iostream> #include <type_traits> template<typename T> concept Arithmetic = std::is_arithmetic<T>::value; template<Arithmetic... Args> bool all(Args... args) { return (... && args); } template<Arithmetic... Args> bool any(Args... args) { return (... || args); } template<Arithmetic... Args> bool none(Args... args) { return !(... || args); } int main(){ std::cout << std::boolalpha << std::endl; std::cout << "all(5, true, 5.5, false): " << all(5, true, 5.5, false) << std::endl; std::cout << "any(5, true, 5.5, false): " << any(5, true, 5.5, false) << std::endl; std::cout << "none(5, true, 5.5, false): " << none(5, true, 5.5, false) << std::endl; }

可変個引数テンプレートで概念を使用できます。関数テンプレートの定義は、折りたたみ式に基づいています。 all、any、および none には、算術の概念をサポートする必要がある型パラメーター T が必要です。算術必須とは、T が整数または浮動小数点のいずれかであることを意味します。

真新しい Microsoft コンパイラ 19.23 は、提案された概念構文を唯一の 1 つとして部分的にサポートします。

その他の要件

もちろん、テンプレート パラメーターに複数の要件を使用できます。

template <SequenceContainer S, 
 EqualityComparable<value_type<S>> T>
Iterator_type<S> find(S&& seq, const T& val){
 ...
}

関数テンプレート find では、コンテナー S が SequenceContainer であり、その要素が EqualityComparable である必要があります。

過負荷

std::advance(iter, n) は、イテレータ iter n の位置をさらに進めます。反復子に応じて、実装はポインター演算を使用するか、さらに n 回進むことができます。最初のケースでは、実行時間は一定です。 2 番目のケースでは、実行時間はステップサイズ n に依存します。概念のおかげで、イテレータ カテゴリで std::advance をオーバーロードできます。

template<InputIterator I>
void advance(I& iter, int n){...}

template<BidirectionalIterator I>
void advance(I& iter, int n){...}

template<RandomAccessIterator I>
void advance(I& iter, int n){...}

// usage

std::vector<int> vec{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto vecIt = vec.begin();
std::advance(vecIt, 5); // RandomAccessIterator

std::list<int> lst{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto lstIt = lst.begin();
std::advance(lstIt, 5); // BidirectionalIterator

std::forward_list<int> forw{1, 2, 3, 4, 5, 6, 7, 8, 9};
auto forwIt = forw.begin();
std::advance(forwIt, 5); // InputIterator

イテレータ カテゴリ、コンテナ std::vector、std::list、および std::forward_list サポートに基づいて、最適な std::advance 実装が使用されます。

スペシャライゼーション

コンセプトは、テンプレートの特殊化もサポートします。

template<typename T>
class MyVector{};

template<Object T>
class MyVector{};

MyVector<int> v1; // Object T
MyVector<int&> v2; // typename T

    <リ>

    MyVector は、制約のないテンプレート パラメーターに移動します。

    <リ>

    MyVector は、制約されたテンプレート パラメーターに移動します。

次は?

私の次の投稿は、C++20 での構文の統一についてです。 C++20 では、C++11 で制約のないプレースホルダー (auto) を使用できた各場所で、制約付きのプレースホルダー (概念) を使用できます。しかし、これで統一は終わりではありません。テンプレートの定義は、C++20 では簡単になります。関数の宣言で、制約付きまたは制約なしのプレースホルダーを使用するだけです。