クラス テンプレートのテンプレート引数推定

前回のテンプレート引数では、関数テンプレートの型推定 (C++98) と自動型推定 (C++11) について書きました。今日はもっとモダンな帽子をかぶっています。非型テンプレート パラメーターとクラス テンプレートの自動型推定 (C++17) から始めて、概念の自動型推定 (C++20) で終了します。

時系列に沿って、C++17 の 2 つの機能から始めましょう。非型テンプレート パラメーターの型推定と、C++17 でのクラス テンプレートの型推定です。

非型テンプレート パラメータの自動型推定

初めに。非型テンプレート パラメータとはこれらは nullptrです 、bool などの整数値 、および int 、左辺値参照、ポインター、列挙型、および C++20 浮動小数点値を使用します。ほとんどの場合、整数型が使用されます。私もそうです。

この理論の後、例から始めましょう。

template <auto N> // (1)
class MyClass{
 ....
};

template <int N> // (2)
class MyClass<N> {
 ....
};


MyClass<'x'> myClass1; // (3)
MyClass<2017> myClass2; // (4)

テンプレート シグネチャの (1) で auto を使用することにより、N は非型のテンプレート パラメーターになります。コンパイラは自動的にそれを推測します。 int (2) を部分的に特化することもできます。テンプレートのインスタンス化 (3) ではプライマリ テンプレート (1) を使用し、次のテンプレートのインスタンス化では int の部分的な特殊化 (4) を使用します。

通常の型修飾子を使用して、非型テンプレート パラメーターの型を制約できます。

template <const auto* p> 
class S;

このクラス テンプレートの宣言では S 、p は const へのポインターである必要があります。

非型テンプレートの自動型推定は、可変個引数テンプレートにも適用できます。

template <auto... ns> 
class VariadicTemplate{ .... }; 

template <auto n1, decltype(n1)... ns>
class TypedVariadicTemplate{ .... };

VariadicTemplate は、任意の数の非型テンプレート パラメーターを推測できます。 TypeVariadicTemplate は、最初のテンプレート パラメーターのみを推測します。残りのテンプレート化されたパラメーターは、最初のタイプ decltype(n1) などの同じタイプになります。 .

クラス テンプレートからの自動型推定により、使用クラス テンプレートが非常に快適になります。

クラス テンプレートの自動型推定

関数テンプレートは、関数の引数から型パラメーターを推定できます。しかし、それは特別な関数、つまりクラス テンプレートのコンストラクターでは不可能でした。 C++17 では、このステートメントは単純に間違っています。コンストラクターは、コンストラクターの引数から型パラメーターを推測できます。これが最初の例です。

// templateArgumentDeduction.cpp

#include <iostream>

template <typename T>
void showMe(const T& t) {
 std::cout << t << '\n';
}

template <typename T>
struct ShowMe{
 ShowMe(const T& t) {
 std::cout << t << '\n';
 }
};

int main() {
 
 std::cout << '\n';
 
 showMe(5.5); // not showMe<double>(5.5);
 showMe(5); // not showMe<int>(5);
 
 ShowMe(5.5); // not ShowMe<double>(5.5);
 ShowMe(5); // not ShowMe<int>(5);
 
 std::cout << '\n';
 
}

main について一言言わせてください 関数。関数テンプレート showMe のインスタンス化 は最初の C++ 標準 C++98 以降有効ですが、クラス テンプレートのインスタンス化 ShowMe C++17以降。ユーザーの観点からは、関数テンプレートまたはクラス テンプレートの使用は、通常の関数またはクラスのように感じられます。

たぶん、あなたは納得していません。クラス テンプレートの引数推定のその他の例を次に示します。

// classTemplateArgumentDeduction.cpp

#include <array>
#include <vector>
#include <mutex>
#include <memory>

int main() {
 
 std::array myArr{1, 2, 3}; // deduces std::array<int, 3> 
 std::vector myVec{1.5, 2.5}; // deduces std::vector<double>
 
 std::mutex mut;
 std::lock_guard myLock(mut); // deduces std::lock_guard<mutex>(mut);
 
 std::pair myPair(5, 5.5); // deduces std::pair<int, double>
 std::tuple myTup(5, myArr, myVec); // deduces std::tuple<int, 
 // std::array<int, 3>, std::vector<double>>
}

コメントは、C++17 コンパイラによって型が推定されることを示しています。 C++ Insights のおかげで、このテンプレート引数推定のプロセスを視覚化できます。

std::pair と std::tuple の最後の 2 つの例は非常に興味深いものです。 C++17 より前は、std::make_pair や std::make_tuple などのファクトリ関数を使用して、型パラメーターを指定せずに std::pair または std::tuple を作成していました。クラス テンプレートとは対照的に、コンパイラは関数の引数から型パラメーターを推測できます。これは std::pair の簡略版です .

// makePair.cpp
#include <utility> template<typename T1, typename T2> std::pair<T1, T2> make_pair2(T1 t1, T2 t2) { return std::pair<T1, T2>(t1, t2); } int main() { auto arg{5.5}; auto pair1 = std::make_pair(5, arg); auto pair2 = make_pair2(5, arg); auto pair3 = std::pair(5, arg); }

コンパイラは pair1 に対して同じ型を推測します そして pair2 . C++17 では、このファクトリ関数は不要になり、std::pair のコンストラクタを直接呼び出すことができます。 pair3 を取得する .

C++ Insights でプログラムを学習できます。

私の関数テンプレート make_pair2 引数を値で取りました。 std::make_pair 引数を減衰させ、関数テンプレート make_pair2も減衰させます .関数の引数の減衰については、前回の記事「テンプレートの引数」で書きました。

概念を使用した自動型推定について少し説明する前に、明示的であることを強調しておきたいと思います。自動型推論は便利です。セキュリティ機能です。タイプを指定しないとエラーになりません。

// automaticTypeDeduction.cpp

#include <string>

template<typename T>
void func(T) {};

template <typename T>
struct Class{
 Class(T){}
};

int main() {
 
 int a1 = 5.5; // static_cast<int>(5.5)
 auto a2 = 5.5;
 
 func<float>(5.5); // static_cast<float>(5.5)
 func(5.5);
 
 Class<std::string> class1("class"); // calls essentially std::string("class")
 Class class2("class");
 
}

すべてのエラーは、型を明示的に指定したことが原因です:

  • int a1 double からの縮小変換をトリガーします intまで
  • func<float>(5.5) double からの変換を引き起こします 値 5.5 float
  • Class<std::string> class1("class") C 文字列で初期化された C++ 文字列を作成します。

プログラムを学習したい場合は、C++ Insights をご覧ください。

概念が登場したときの自動型推定の話に追加することはあまりありません。

コンセプトによる自動型推定

概念を使用した自動型推定は期待どおりに機能します:

// typeDeductionConcepts.cpp

#include <concepts>

void foo(auto t) {} // (1)

void bar(std::integral auto t){} // (2)

template <std::regular T> // (3)
struct Class{
 Class(T){}
};

int main() {

 foo(5.5);
 bar(5);
 Class cl(true);

}

制約のないプレースホルダーを使用するかどうか ( auto 1 行目)、制約付きプレースホルダー (2 行目の概念)、または制限されたテンプレート パラメーター (3 行目の概念) の場合、コンパイラは期待される型を推測します。 C++ Insights は型推定を視覚化するのに役立ちます。

次は?

次回の投稿では、テンプレートの次のエキサイティングな機能である特殊化について書きます。関数テンプレートまたはクラス テンプレートを完全に特殊化できます。さらに、クラス テンプレートは部分的に特殊化できます。