コンセプト

私たちは2020年に滞在します。高い確率で、コンセプトを取得します。もちろん、将来についてのウォータープルーフの声明は難しいですが、声明は Bjarne Stroustrup からのものです (Meeting C++ 2016 at Berlin)。

古典的な概念

テンプレートを使用した汎用プログラミングの重要なアイデアは、さまざまな型で使用できる関数とクラスを定義することです。しかし、間違った型でテンプレートをインスタンス化することがよくあります。その結果、何ページにもわたる不可解なエラー メッセージが表示される場合があります。悲しいことに、C++ のテンプレートはこれで知られています。したがって、古典的な概念は C++11 の優れた機能の 1 つとして計画されました。コンパイラによって検証できるテンプレートの制約を指定できるようにする必要があります。それらの複雑さのおかげで、2009 年 7 月に標準から削除されました。 (Bjarne Stroustrup)

コンセプト

C++20 では概念が得られます。概念は最初の実装では単純化された古典的な概念ですが、提供できることはたくさんあります。

彼ら

<オール>
  • プログラマーがインターフェースの一部として要件を直接表現できるようにする
  • テンプレート パラメータの要件に基づいて、関数のオーバーロードとクラス テンプレートの特殊化をサポートします。
  • テンプレート パラメータの要件と適用されたテンプレート引数を比較することで、大幅に改善されたエラー メッセージを生成します。
  • ジェネリック プログラミングのプレースホルダーとして使用できます。
  • 独自のコンセプトを定義できるようにする
  • コンセプトはコンセプト ライトと呼ばれることもありますが、その機能は決してライトではなく、1 回の投稿で紹介することはできません。したがって、ポイント4と5は後の投稿に延期します。約束しました!

    プログラムのコンパイル時間や実行時間を追加することなく、メリットが得られます。概念は Haskell の型クラスに似ています。概念は、構文上の制限ではなく、セマンティック カテゴリを説明します。標準ライブラリの型については、DefaultConstructible、MoveConstructible、CopyConstructible、MoveAssignable、CopyAssignable、または Destructible などのライブラリの概念を取得します。コンテナーについては、ReversibleContainer、AllocatorAwareContainer、SequenceContainer、ContinousContainer、AssociativeContainer、または UnorderedAssociativeContainer などの概念を取得します。概念とその制約については、cppreference.com で読むことができます。

    概念を説明する前に、Haskell の型クラスを見てみましょう。

    Haskell の型クラス

    型クラスは、同様の型のインターフェイスです。型が型クラスのメンバーである場合、特定のプロパティが必要です。型クラスは、オブジェクト指向プログラミングでインターフェイスが果たす役割と同様の役割を、ジェネリック プログラミングで果たします。 Haskell の型クラス階層の一部をここで見ることができます。

    型クラス Eq のメンバーである場合、その型にとって特別なことは何ですか? Eq は平等を表し、そのメンバーに次のことを要求します:

    class Eq a where
     (==) :: a -> a -> Bool
     (/=) :: a -> a -> Bool
     a == b = not (a /= b)
     a /= b = not (a == b)
    

    Eq では、その型が関数の等価 (==) と不等価 (/=) をサポートする必要があります。式 a -> a -> Bool は、関数のシグネチャを表します。この関数は、2 つの同一の型 a を取り、ブール値を返します:Bool.ただし、具象型の場合、等式は不等式にマッピングされ、その逆も同様であるため、等式または不等式を実装するだけで十分です。両方の関数のデフォルトの実装は、最後の 2 行で提供されます。

    次の抜粋されたコードにより、組み込み型 Bool は型クラス Eq のインスタンスになります。

    instance Eq Bool where
     True == True = True
     False == False = True
     _ == _ = False
    

    Haskell の型クラスは階層を構築します。型クラス Ord は、型クラス Eq のサブクラスです。したがって、型クラス Ord のインスタンスは、型クラス Eq のメンバーである必要があり、さらに比較演算子をサポートする必要があります。

    Haskell は、ある種のクラスに必要な関数を自動的に作成できます。したがって、データ型 day の朝と午後の値が等しいかどうかを比較して出力できます。型クラス Eq と Show から Day を導出するだけです。

    data Day= Morning | Afternoon
     deriving (Eq,Show)
    

    これで、対話型 Haskell シェルでデータ型 Day を直接テストできます。対話型シェルの正式な名前は REPL です。 Python や Perl などの多くのプログラミング言語には REPL があります。 REPL は R の略です ead E P を評価する リント L おっと.

    Haskell の型クラスには、さらに多くの機能があります。たとえば、独自のタイプのクラスを定義できます。

    関数、クラス、およびクラスのメンバーの概念

    概念はテンプレート宣言の一部です。

    関数

    関数テンプレートの並べ替えには

    template<Sortable Cont>
    void sort(Cont& container){...}
    
    

    コンテナはソート可能でなければなりません。テンプレート パラメーターの要件をより明示的に定義することもできます。

    template<typename Cont>
     requires Sortable<Cont>()
    void sort(Cont& container){...}
    

    Sortable は、述語である定数式でなければなりません。つまり、式はコンパイル時に評価可能で、ブール値を返す必要があります。

    ソート不可能なコンテナ lst でソート アルゴリズムを呼び出すと、コンパイラから固有のエラー メッセージが表示されます。

    std::list<int> lst = {1998,2014,2003,2011};
    sort(lst); // ERROR: lst is no random-access container with <
    

    あらゆる種類のテンプレートにコンセプトを使用できます。

    クラス

    したがって、テンプレート引数としてオブジェクトのみを受け入れるクラス テンプレート MyVector を定義できます。

    template<Object T>
    class MyVector{};
    
    MyVector<int> v1; // OK
    MyVector<int&> v2 // ERROR: int& does not satisfy the constraint Object
    

    ここで、コンパイラは、a ポインター (int&) がオブジェクトではないと不平を言います。 MyClass はさらに調整できます。

    クラスのメンバー

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

    MyVector のメソッド push_back では、テンプレート引数がコピー可能である必要があります。

    拡張機能

    テンプレートは、そのテンプレート パラメータに対して複数の要件を持つことができます。

    複数の要件

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

    関数テンプレート find には 2 つの要件があります。一方では、コンテナーはその要素を線形配置 (SequenceContainer) で格納する必要があり、他方では、コンテナーの要素は同等である必要があります:EqualityComparable>).

    概念は関数のオーバーロードをサポートします。

    関数のオーバーロード

    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){...}
    
    std::list<int> lst{1,2,3,4,5,6,7,8,9};
    std::list<int>:: iterator i= lst.begin();
    std::advance(i,2); // BidirectionalIterator
    

    関数テンプレートの Advance は、その反復子 iter n の位置をさらに進めます。それに応じて、イテレータがフォワードの場合、双方向のランダム アクセス イテレータのさまざまな関数テンプレートが適用されます。 std::list を使用すると、BidirectionalIterator が選択されます。

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

    クラス テンプレートの特殊化

    template<typename T>
    class MyVector{};
    
    template<Object T>
    class MyVector{};
    
    MyVector<int> v1; // Object T
    MyVector<int&> v2 // typename T
    

    したがって、コンパイラは MyVector v2 を最初の行の一般的なテンプレートにマップします。コンパイラは、特殊化テンプレート class MyVector{} とは対照的に、MyVector v1 をマップします。

    次は?

    Haskell には型クラス Monad があります。既知のインスタンスは Maybe モナドです。なぜ私はそのことについて書いたのですか?それは簡単です。 C++17 は、データ型 std::optional を使用して、結果を返すことができる、またはできない計算を表すモナドを取得します。 std::optional の詳細については、次の投稿で説明します。