テンプレート引数

コンパイラがテンプレート引数の型を推測する方法は非常に興味深いものです。短くするために、ほとんどの場合、期待するタイプが得られます。ルールは関数テンプレート (C++98) だけでなく、auto にも適用されます。 (C++11)、クラス テンプレート (C++17)、概念 (C++20) へ。

C++ は、最初から関数テンプレートの引数推定をサポートしています。ここに短い要約があります。

関数テンプレートの引数推定

関数テンプレート max を呼び出してみましょう int の場合 とダブル

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

int main() {
 
 max(10, 5); // (1)
 max(10.5, 5.5); // (2)
 
}

この場合、コンパイラは関数の引数からテンプレートの引数を推測します。 C++ Insights は、コンパイラが max の完全に特殊化された関数テンプレートを作成することを示しています int の場合 (1) double の場合 (2).

この場合のようなテンプレート型推論のプロセスは、ほとんどの場合、期待される型を生成します。このプロセスをより深く分析することは、非常に啓発的です。

テンプレート タイプの推定

テンプレート タイプを推測するとき、T、ParameterType、および式の 3 つのエンティティが関係します。

template <typename T>
void func(ParameterType param);

func(expression);

2 つのタイプが推定されます:

  • T
  • ParameterType

ParameterType

  • 価値
  • 参照 (&) またはポインタ (*)
  • ユニバーサル リファレンス (&&)

expression 左辺値または右辺値を持つことができます。さらに、左辺値または右辺値は参照、または const にすることができます /volatile

テンプレート型推定プロセスを理解する最も簡単な方法は、 ParameterType を変更することです。 .

ParameterType は値です

値によるパラメーターの取得は、おそらく最も使用されるバリアントです。

template <typename T>
void func(T param);

func(expr);

  • exprの場合 は参照です。参照は無視されます => newExpr 作成されます
  • newExprの場合 const です または volatileconst または volatile は無視されます。

ParameterType が参照またはユニバーサル参照の場合、expr の constness (または volatileness)

ParameterType は参照 (&) またはポインタ (*) です

簡単にするために、参照を使用します。同様の議論がポインターにも当てはまります。基本的に、期待どおりの結果が得られます。

template <typename T>
void func(T& param);
// void func(T* param);

func(expr);

  • exprの場合 が参照の場合、参照は無視されます (ただし、最後に追加されます)。
  • expr は ParameterType に一致します 結果の型は参照になります。つまり、
    • an expr タイプ int int& になります
    • an expr タイプ const intconst int& になります
    • an expr タイプ const int&const int& になります

ParameterType はユニバーサル リファレンス (&&) です

template <typename T>
void func(T&& param);

func(expr);

  • exprの場合 左辺値の場合、結果の型は左辺値参照になります。
  • exprの場合 右辺値の場合、結果の型は右辺値参照になります。

確かに、この説明はかなり技術的でした。以下に例を示します。

// templateTypeDeduction.cpp

template <typename T>
void funcValue(T param) { }

template <typename T>
void funcReference(T& param) { }

template <typename T>
void funcUniversalReference(T&& param) { }

class RVal{};

int main() {

 const int lVal{};
 const int& ref = lVal;
 
 funcValue(lVal); // (1)
 funcValue(ref);
 
 funcReference(lVal); // (2)
 
 funcUniversalReference(lVal); // (3)
 funcUniversalReference(RVal());

}

値 (1)、参照 (2)、およびユニバーサル参照 (3) によって引数を取る関数テンプレートを定義して使用します。

C++ Insights のおかげで、コンパイラの型推定を視覚化できます。

  • (1) :funcValue の両方の呼び出し 関数テンプレートの同じインスタンス化を引き起こします。推定される型は int です .

  • (2) :関数 funcReference の呼び出し const int&で タイプ const int&を与える .

  • (3) :関数 funcUniversalReference の使用 左辺値参照または右辺値参照を指定してください。

関数 funcValue を呼び出すと、興味深い事実が 1 つあります。 Cアレイ付き。 C アレイは崩壊します。

C アレイの減衰

値による C 配列の取得は特別です。

// typeDeductionArray.cpp

template <typename T>
void funcValue(T param) { }

int main() {

 int intArray[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 funcValue(intArray);
 
}

関数テンプレート funcValue を呼び出す場合 C 配列では、C 配列はその最初の要素へのポインターに減衰します。崩壊には多くの側面があります。関数の引数が値渡しの場合に適用されます。 Decay は、関数からポインター、配列からポインター、または左辺値から右辺値への暗黙的な変換が適用されることを意味します。さらに、型 T の参照とその const-volatile 修飾子が削除されます。

これは、C++ Insights のプログラムのスクリーンショットです。

これは基本的に、C 配列のサイズがわからないことを意味します。

しかし、トリックがあります。参照によって C 配列を取得し、C 配列の型とサイズでパターン マッチングを行うと、C 配列のサイズが得られます。

// typeDeductionArraySize.cpp

#include <cstddef>
#include <iostream>

template <typename T, std::size_t N>
std::size_t funcArraySize(T (&arr)[N]) { 
 return N;
}

int main() {

 std::cout << '\n';

 int intArray[10]{ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

 funcArraySize(intArray);

 std::cout << "funcArraySize(intArray): " << funcArraySize(intArray) << '\n';

 std::cout << '\n';
 
}

関数テンプレート funcArraySize C 配列のサイズを推定します。読みやすくするために、C 配列パラメーターに arr: std::size_t funcArraySize(T (&arr)[N]) という名前を付けました .これは必要ありません。 std::size_t funcArraySize(T (&)[N]) と書くだけでかまいません。 . C++ Insights の内部情報は次のとおりです。

最後に、プログラムの出力:

テンプレート型推論を理解すると、基本的に auto を理解できます C++11 での型推定。

auto 型推論

auto 型推定は、テンプレート型推定のルールを使用します。

思い出していただきたいのですが、これらはテンプレート型推定の必須エンティティです:

template <typename T> 
void func(ParameterType param);

auto val = 2011;

auto を理解する auto を考慮する必要があることを意味します T の代替として auto の型指定子 ParameterType の代替として 関数テンプレートで。

型指定子は、値 (1)、参照 (2)、またはユニバーサル参照 (3) にすることができます。

auto val = arg; // (1)

auto& val = arg; // (2)

auto&& val = arg; // (3)

試しに前回のプログラム templateTypeDeduction.cpp を変更してみましょう auto を使用します 関数テンプレートの代わりに。

// autoTypeDeduction.cpp

class RVal{};

int main() {

 const int lVal{};
 const int& ref = lVal;
 
 auto val1 = lVal; // (1)
 auto val2 = ref;
 
 auto& val3 = lVal; // (2)
 
 auto&& val4 = lVal; // (3)
 auto&& val5 = RVal();

}

結果の型を C++ Insights で調べると、プログラム templateTypeDeduction.cpp で推定された型と同じであることがわかります。 .

もちろん、auto 値によって C 配列を取る場合にも減衰します。

新しい pdf-Bundle の準備ができました:C++20 コルーチン

pdf-bundle を用意しました。それを取得するのは非常に簡単です。私のドイツ語または英語のニュースレターを購読すると、pdf バンドルへのリンクが表示されます。 PDF バンドルの詳細については、C++ コルーチンを参照してください。

次は?

C++17 では、型推論がより強力になります。 1 つ目は、非型テンプレート パラメーターの自動型推定が可能であり、2 つ目は、クラス テンプレートもその引数を推定できることです。特に、クラス テンプレートの引数推定は、プログラマーの作業を大幅に簡素化します。