C++ コア ガイドライン:テンプレートとジェネリック プログラミングのルール

この記事では、C++ でのジェネリック プログラミングのルールを紹介します。私の観点からすると、ジェネリック プログラミングは C++ の優れた機能であり、未来でもあります。したがって、これと今後の投稿は C++ の将来に関するものです。

まず第一に、私はテンプレートとジェネリック プログラミングという用語を使用します。もちろん、テンプレートは一般的なコードを記述するための単なる手段であることは知っています。 C++ のテンプレートが何であるかは知っているが、ジェネリック プログラミングが何を意味するかは知らないと思いますか?ウィキペディアからの私のお気に入りの定義は次のとおりです。

  • ジェネリック プログラミング to-be-specified-later 型の観点からアルゴリズムを記述するコンピューター プログラミングのスタイルです。 インスタンス化されます パラメーターとして提供される特定の型に必要な場合

テンプレートのルールは、現在の C++17 と今後の C++20 標準に関するものです。もちろん、C++20 で概念が得られると思います。要するに、概念、テンプレート インターフェイス、テンプレート定義、テンプレート階層、可変個引数テンプレート、およびテンプレート メタプログラミングに対する 100 のルールがあります。最初の 5 つのルールは非常に一般的です。

  • T.1:テンプレートを使用してコードの抽象化のレベルを上げる
  • T.2:テンプレートを使用して、多くの引数の型に適用されるアルゴリズムを表現します
  • T.3:テンプレートを使用してコンテナーと範囲を表現する
  • T.5:一般的な手法と OO 手法を組み合わせて、コストではなく強みを増幅する

例では、概念がコメントアウトされていることがよくあります。それらを試してみたい場合は、それらをコメント化し、フラグ -fconcepts を指定して少なくとも GCC 6.1 コンパイラを使用してください。 またはオンライン コンパイラ:制約と概念。

コンセプトは、コンパイル時に評価されるテンプレートの述語です。 Number,  などのセマンティック カテゴリをモデル化する必要があります。 Callable, Iterator または Range HasPlus, などの構文上の制限はありません または IsInvocable. 概念の詳細はこちらです。

おそらく、セマンティック カテゴリと構文上の制限の違いに困惑しているでしょう。最初の規則は、両方の用語を区別するのに役立ちます。

T.1:テンプレートを使用してコードの抽象化レベルを上げる

これはガイドラインの例ですが、2番目の概念を Addable. と呼びました

template<typename T>
 // requires Incrementable<T>
T sum1(vector<T>& v, T s)
{
 for (auto x : v) s += x;
 return s;
}

template<typename T>
 // requires Addable<T>
T sum2(vector<T>& v, T s)
{
 for (auto x : v) s = s + x;
 return s;
}

両方の概念の何が問題になっていますか?どちらの概念も具体的すぎます。どちらの概念も、インクリメントや + 操作などの特定の操作に基づいています。構文上の制約からさらに一歩進んで、セマンティック カテゴリ Arithmetic. に進みましょう。

template<typename T>
 // requires Arithmetic<T>
T sum(const vector<T>& v, T s)
{
 for (auto x : v) s += x;
 return s;
}

現在、アルゴリズムには最小限の要件があります。保留:アルゴリズムは優れていますが、良くありません。 std::vector でのみ動作します .コンテナのタイプでは一般的ですが、コンテナでは一般的ではありません。合計アルゴリズムをもう一度一般化しましょう。

template<typename Cont, typename T>
 // requires Container<Cont>
 // && Arithmetic<T>
T sum(const Cont& v, T s)
{
 for (auto x : v) s += x;
 return s;
}

さて、大丈夫です。合計のより簡潔な定義を好むかもしれません。キーワード typename の代わりに、概念を直接使用します。

template<Container Cont, Arithmetic T>
T sum(const Cont& cont, T s){
 for (auto x : cont) s += x;
 return s;
}

T.2:テンプレートを使用して、多くの引数の型に適用されるアルゴリズムを表現する

std::find の最初のオーバーロードを調べると、 cppreference.com では、次のようになっています:

template< class InputIt, class T >
InputIt find( InputIt first, InputIt last, const T& value );

イテレータの型は、名前にエンコードされています:InputIt 入力イテレータを表し、ポイント先の要素から読み取ることができるイテレータであることを意味します。この宣言には 2 つの問題があります:

<オール>
  • イテレータの要件は名前にエンコードされています。これは、悪名高いハンガリー語の表記法を思い出させます。
  • ポイント先の要素を値と比較できるという要件はありません。
  • イテレータの概念を直接使用してみましょう:

    template<Input_iterator Iter, typename Val>
     // Equality_comparable<Value_type<Iter>, Val>
    Iter find(Iter b, Iter e, Val v)
    {
     // ...
    }
    

    T.3:テンプレートを使用してコンテナーと範囲を表現する

    わかった。コンテナーをジェネリックにすることは明らかです。たとえば、これは Vector. です

    template<typename T>
     // requires Regular<T>
    class Vector {
     // ...
     T* elem; // points to sz Ts
     int sz;
    };
    
    Vector<double> v(10);
    v[7] = 9.9;
    

    わかりましたが、ユーザー定義型 T はいつですか 通常?ドキュメント Fundamentals of Generic Programming は、bool, などの組み込み型のように動作する場合、型 T 正規を定義します。 int, または double. 私はそれを言及する必要があります。ジェネリック プログラミングの基礎に関する論文は、James C. Dehnert と Alexander Stepanow によるものです。アレクサンダー・ステファノフの名前はすでにご存知だと思います。彼は標準テンプレート ライブラリの有名な父です。

    ドキュメントには、次の操作が定義されている場合、型 T は通常と呼ばれると記載されています:

    T に対する等値、不等値、および順序付け操作は、コンポーネントごとに定義できます。

    次は?

    私の最初の計画は、ルール 5 について書くことでした。ルール 5 は非常に短いため、計画を変更し、この手法の使用例として型消去について言及しました。型消去は、単一のインターフェイスを介してさまざまな具象型を表現する手法です。テンプレートを使用した型消去は、数文では説明できません。したがって、この挑戦​​的なテクニックについては、次の投稿で書きます。