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

前回の投稿 テンプレートの専門化 でテンプレートの専門化の基本を紹介した後、今日はさらに深く掘り下げます。クラス テンプレートの部分的および完全な特殊化をコンパイル時の if として提示したいと考えています。

コンパイル時 if としてのクラス テンプレートの特殊化

前回のブログ投稿テンプレートの専門化の後、いくつかの同様の質問を受けました。型が特定の型であるか、2 つの型が同じであるかをどのように判断できますか?これらの質問に答えるのは思ったより簡単で、クラス テンプレートの特殊化に関するより多くの理論を提示するのに役立ちます。これらの質問に答えるために、std::is_same の簡易バージョンを実装します。 および std::remove_reference です。この記事で紹介する手法は、クラス テンプレートの特殊化の応用であり、コンパイル時の if.

std::is_same

std::is_same type-traits ライブラリの関数です。両方の型が同じ場合は std::true_type を返し、そうでない場合は std::false を返します。 _タイプ。簡単にするために、true を返します。 または false .

// isSame.cpp

#include <iostream>

template<typename T, typename U> // (1)
struct isSame {
 static constexpr bool value = false;
};
 
template<typename T> // (2)
struct isSame<T, T> {
 static constexpr bool value = true;
}; 

int main() {

 std::cout << '\n'; 

 std::cout << std::boolalpha;
 // (3)
 std::cout << "isSame<int, int>::value: " << isSame<int, int>::value << '\n';
 std::cout << "isSame<int, int&>::value: " << isSame<int, int&>::value << '\n';
 
 
 int a(2011);
 int& b(a); // (4)
 std::cout << "isSame<decltype(a), decltype(b)>::value " << 
 isSame<decltype(a), decltype(b)>::value << '\n';

 std::cout << '\n';

}

プライマリ テンプレート (1) は、デフォルトの false として返されます 、その value. を要求すると 逆に、両方の型が同じ場合に使用される部分特殊化 (2) は true を返します。 .クラステンプレート isSame を使用できます 型 (3) について、そして decltype のおかげで 、値について (4)。次のスクリーンショットは、プログラムの出力を示しています。

あなたはすでにそれを推測しているかもしれません。クラス テンプレート isSame テンプレートメタプログラミングの例です。さて、ちょっと回り道をして、メタについて少し書きます。

メタ関数とメタデータ

実行時には、データと関数を使用します。コンパイル時に、メタデータとメタ関数を使用します。非常に簡単です。メタプログラミングを行うのでメタと呼ばれますが、メタデータまたはメタ関数とは何ですか?これが最初の定義です。

  • メタデータ :メタ関数で使用される型と整数値。
  • メタ関数 :コンパイル時に実行される関数。

メタデータとメタ関数という用語について詳しく説明しましょう。

メタデータ

メタデータには 3 つのエンティティが含まれます:

<オール>
  • int、double、 std::string などの型
  • C++20 での整数、列挙子、ポインタ、左辺値参照、浮動小数点値などの非型
  • テンプレート
  • これまでのところ、メタ関数 isSame で型のみを使用しました .

    メタ関数

    クラステンプレート isSame などの型 関数をシミュレートするためにテンプレートのメタプログラミングで使用されます。私のメタ関数の定義に基づくと、constexpr 関数はコンパイル時にも実行できるため、メタ関数です。

    メタ関数 値を返すだけでなく、型を返すこともできます。慣例により、メタ関数は ::value を介して using を返します 、および ::type を使用するタイプ .次のメタ関数 removeReference 結果として型を返します。

    // removeReference.cpp
    
    #include <iostream>
    #include <utility>
    
    template<typename T, typename U> 
    struct isSame {
     static constexpr bool value = false;
    };
     
    template<typename T> 
    struct isSame<T, T> {
     static constexpr bool value = true;
    }; 
    
    template<typename T> // (1)
    struct removeReference { 
     using type = T;
    };
    
    template<typename T> // (2)
    struct removeReference<T&> {
     using type = T;
    };
    
    template<typename T> // (3)
    struct removeReference<T&&> {
     using type = T;
    };
    
    int main() {
    
     std::cout << '\n';
    
     std::cout << std::boolalpha;
     // (4) 
     std::cout << "isSame<int, removeReference<int>::type>::value: " << 
     isSame<int, removeReference<int>::type>::value << '\n';
    
     std::cout << "isSame<int, removeReference<int&>::type>::value: " << 
     isSame<int, removeReference<int&>::type>::value << '\n';
    
     std::cout << "isSame<int, removeReference<int&&>::type>::value: " << 
     isSame<int, removeReference<int&&>::type>::value << '\n';
    
    
     // (5)
    
     int a(2011);
     int& b(a); 
     std::cout << "isSame<int, removeReference<decltype(a)>::type>::value: " << 
     isSame<int, removeReference<decltype(a)>::type>::value << '\n';
    
     std::cout << "isSame<int, removeReference<decltype(b)>::type>::value: " << 
     isSame<int, removeReference<decltype(b)>::type>::value << '\n';
    
     std::cout << "isSame<int, removeReference<decltype(std::move(a))>::type>::value: " << 
     isSame<int, removeReference<decltype(std::move(a))>::type>::value << '\n';
    
     std::cout << '\n';
    
    }
    

    この例では、以前に定義したメタ関数 isSame を適用します。 およびメタ関数 removeReference .プライマリ テンプレート removeReference (1) 名前 type を使用して T を返します .左辺値参照 (2) と右辺値参照の部分的な特殊化も、テンプレート パラメーターから参照を削除することによって T を返します。前と同じように、メタ関数 removeReference を使用できます タイプ (4) と、decltype のおかげで 、値 (5)。 decltype(a)decltype(b) を返します 左辺値参照、および decltype(std::move(a)) を返します 右辺値参照を返します。

    最後に、これがプログラムの出力です。

    私が陥る罠が一つあります。クラス外で完全に特殊化されたクラス テンプレートのメンバー関数を定義する場合、 template<> を使用してはなりません。 .

    クラス本体外で定義された特殊化のメンバー関数

    次のコード プログラムは、クラス テンプレート Matrix を示しています。 、部分的および完全な専門化を持っています。

    // specializationExtern.cpp
    
    #include <cstddef>
    #include <iostream>
    
    template <typename T, std::size_t Line, std::size_t Column> // (1)
    struct Matrix;
    
    template <typename T> // (2)
    struct Matrix<T, 3, 3>{
     int numberOfElements() const;
    };
    
    template <typename T>
    int Matrix<T, 3, 3>::numberOfElements() const {
     return 3 * 3;
    };
    
    template <> // (3)
    struct Matrix<int, 4, 4>{
     int numberOfElements() const;
    };
    
    // template <> // (4)
    int Matrix<int, 4, 4>::numberOfElements() const {
     return 4 * 4;
    };
    
    int main() {
    
     std::cout << '\n';
    
     Matrix<double, 3, 3> mat1; // (5)
     std::cout << "mat1.numberOfElements(): " << mat1.numberOfElements() << '\n';
    
     Matrix<int, 4, 4> mat2; // (6)
     std::cout << "mat2.numberOfElements(): " << mat2.numberOfElements() << '\n';
    
     std::cout << '\n';
     
    }
    

    (1) プライマリ テンプレートを宣言します。 (2) 部分的な特殊化を定義し、(3) Matrix の完全な特殊化を定義します。 .メンバー関数 numberOfElements クラス本体の外で定義されます。行 (4) はおそらく非直感的な行です。メンバ関数 numberOfElements を定義する場合 クラス本体の外では、 template <> を使用してはなりません .行 (5) は部分のインスタンス化を引き起こし、行 (6) は完全な特殊化のインスタンス化を引き起こします。

    次は?

    次の投稿では、関数テンプレートの完全な特殊化と、関数との驚くべき相互作用について書きます。簡単に言うと、C++ コア ガイドラインによると、T.144:関数テンプレートを特殊化しないでください。