テンプレート - 最初のステップ

この投稿のアイデアは非常にシンプルです。テンプレート、特にテンプレートのインスタンス化のプロセスを視覚化したいと考えています。 C++ Insights のおかげで、この視覚化は非常に簡単です。

テンプレート (クラス テンプレートまたは関数テンプレート) は、クラスまたは関数のファミリです。テンプレートをインスタンス化するときは、これらのクラスまたは関数のファミリから具象クラスまたは具象関数を作成します。これが最初の簡単な質問です。答えたいと思います。簡単にするために、クラス テンプレートをジェネリック クラスと呼び、関数テンプレートをジェネリック関数と呼ぶことがあります。

いつテンプレートを使用する必要がありますか?

関数またはクラスがそのような一般的なアイデアを表し、このアイデアが具象型にバインドされていない場合は、テンプレートを使用する必要があります。たとえば、max などの関数 または vector などのコンテナ

テンプレートを作成するにはどうすればよいですか?

関数 max を実装していると思います 2 つの int を受け入れます。

int max(int lhs, int rhs) {
 return (lhs > rhs)? lhs : rhs;
}

関数からテンプレートを作成することは、一般的に簡単です。

<オール>
  • template <typename T> という行を挿入します 関数の前
  • 具象型 int を置き換えます 型パラメータ T で .
  • template <typename T> // (1)
    T max(T lhs, T rhs) { // (2)
     return (lhs > rhs)? lhs : rhs;
    }
    

    2 つの追加のコメントをマークする必要があります。まず、名前 typename の代わりに 、 class も使用できます . typename を強くお勧めします 、なぜなら T クラスであってはなりませんが タイプ、非タイプ、またはテンプレートにすることができます。次に、慣例により、 T を使用します 最初の型パラメーターの名前として。

    クラスをクラス テンプレートに変換する場合も、同じ手順が機能します。

    今、まさに C++ Insights が貴重なサービスを提供してくれるところまで来ました。

    テンプレートをインスタンス化するとどうなりますか?

    int の関数テンプレートの最大値をインスタンス化しましょう と double .

    template <typename T>
    T max(T lhs, T rhs) {
     return (lhs > rhs)? lhs : rhs;
    }
    
    int main() {
     
     max(10, 5);
     max(10.5, 5.5);
     
    }
    

    C++ Insights は、このテンプレートのインスタンス化の自動プロセスについてより深い洞察を提供します。

    テンプレートのインスタンス化のプロセスにより、6 行目から 23 行目が作成されます。関数 max のインスタンス化について少し書きましょう。 2 つの int について (6 行目から 13 行目)。スクリーンショットの 6 行目は、ソース ファイルの 8 行目 (max(10, 5)) を表しています。 ) により、6 行目から 13 行目が生成されます。コンパイラが生成するコードの最初の 2 行が最も興味深いものだと思います。

    template<>
    int max<int>(int lhs, int rhs)
    {
     return (lhs > rhs) ? lhs : rhs;
    }
    

    max int: max<int> の完全に特殊化された関数テンプレートです。 .汎用部分が空です: template<> .コンパイラは max のファミリから生成します -functions int の 1 つの具体的な関数 .これは、使用される型ごとにコンパイラが具体的な関数を生成するという意味でもありますか?

    同じテンプレートを複数回インスタンス化するとどうなるかタイプしますか?

    次の例は、クラス テンプレートに基づいています。 int に対して 2 回インスタンス化された単純なコンテナーを次に示します。 .

    template <typename T, int N>
    class Array{
     public:
     int getSize() const{
     return N;
     }
     private:
     T elem[N];
    };
    
    int main() {
     
     Array<int, 5> myArr1; // (1)
     Array<int, 10> myArr2; // (2)
     Array<int, 5> myArr3; // (3)
     
    }
    

    Array<int, 5> を 2 回インスタンス化しました (行 (1) と (3)) と 1 回 Array<int, 10> (2行目)。 C++ Insights の出力を調べると、Array<int, 5> の 2 番目のインスタンス化が (3 行目) は、(1 行目) によって既にトリガーされた最初のインスタンス化を使用します。出力の関連部分は次のとおりです。

    この例は終わりましたか?いいえ!さらに興味深い観察が 2 つあります。

    まず、テンプレートのインスタンス化のプロセスが面倒です。次に、非型のテンプレート パラメーターを使用します。

    テンプレートのインスタンス化が遅延しています

    メンバー関数 getSize を認識しましたか? () はインスタンス化されていませんか?メンバー関数の宣言のみ使用できます。テンプレートのインスタンス化のプロセスは怠惰です。つまり、必要がない場合はインスタンス化されません。これは、メンバー関数で無効なコードを使用できる限り、正常に機能します。もちろん、メンバー関数を呼び出してはなりません。信じられないなら、次の小さなプログラムをコンパイルしてください。最初に行 (1) を無効にし、次に行 (1) を有効にします。

    // number.cpp
    
    #include <cmath>
    #include <string>
    
    template <typename T>
    struct Number {
     int absValue() {
     return std::abs(val);
     }
     T val{};
    };
    
    int main() {
     
     Number<std::string> numb;
     // numb.absValue(); // (1)
     
    }
    

    前のプログラムに戻って getSize() を呼び出しましょう .これが変更された main です プログラム。

    int main() {
     
     Array<int, 5> myArr1; 
     Array<int, 10> myArr2; 
     Array<int, 5> myArr3; 
     myArr3.getSize(); // (1)
     
    }
    

    したがって、次のスクリーンショットは、メンバー関数 getSize() のコンパイラ生成コードを示しています。 (18~21行目)

    int 非型テンプレート パラメータとして

    この例で使用した 2 番目の型パラメーターは、特に int. int です。 非型テンプレート パラメータの例です。 int のほかに 、すべての整数型、浮動小数点型 (C++20) を使用できますが、型以外のテンプレート パラメーターとしてポインターまたは参照も使用できます。長さの異なる 2 つの配列をインスタンス化するとどうなりますか?

    template <typename T, int N>
    class Array{
     public:
     int getSize() const{
     return N;
     }
     private:
     T elem[N];
    };
    
    int main() {
     
     Array<float, 5> myArr1;
     Array<float, 10> myArr2;
     
    }
    

    あなたはおそらくそれを推測しました。 2 つの配列がインスタンス化されます。 C++ Insights からの重要な出力は次のとおりです

    これは、両方のインスタンス化が異なる int を使用することを意味します 値はさまざまなタイプを作成します。

    次のステップ

    テンプレートを使用したこれらの最初のステップの後、次の投稿で関数テンプレートについて深く掘り下げます。