関数テンプレート

関数テンプレートは関数のファミリーです。この投稿では、関数テンプレートについて深く掘り下げたいと思います。

同じページに移動するための短いリマインダーです。

max などの関数テンプレートをインスタンス化する場合 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);
 
}

コンパイラは、int および double 用に完全に特殊化された関数テンプレートを生成します : max<int> および max<double> .ジェネリック部分はどちらの場合も空です: template<> C++ Insights のおかげで、ここに洞察があります。

さて、これで詳細に飛び込むことができます。関数テンプレートと非テンプレート関数 (要するに関数) がオーバーロードするとどうなりますか?

関数テンプレートと関数のオーバーロード

関数 max を使用させてください もう一度。今回は float 用にインスタンス化します そして double, 関数 max を提供します 取ることも 2 倍になります。

これが私の次の例です:

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

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

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

あなたは私の質問を推測するかもしれません。行 (1) と (2) では何が起こるでしょうか?いくつか質問がありますか?

  • 行 (1):コンパイラは関数テンプレートまたは関数を選択し、float をプロモートしますか? double へ .
  • 行 (2):関数と関数テンプレートのいずれかが理想的に適合します。これはあいまいで、コンパイラ エラーを引き起こす可能性があります。

質問への回答は非常に直感的で、C++ の一般的なルールに従います。コンパイラは最適な関数を選択します。

  • 行 (1):関数は float から double への昇格を必要とするため、関数テンプレートの方が適しています。
  • 行 (2):関数テンプレートと関数は理想的に適合しています。この場合、追加のルールが適用されます。両方が同等に適合する場合、コンパイラは関数を優先します。

以前と同様に、C++ Insights はこのプロセスを視覚化するのに役立ちます。

スクリーンショットはそれを明示的に示しています。関数テンプレート max の使用のみ float で (2 行目) 関数テンプレートのインスタンス化をトリガーします。

関数テンプレートの基本を通して、さらに旅を続けましょう。

最初の免責事項:この投稿では概念を無視します。

さまざまなテンプレート引数

関数テンプレート max を使用させてください 異なる型を持つ 2 つの値を持つ。

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

int main() {
 
 max(10.5f, 5.5);
 
}

C++ Insights で試してみましょう:

わお!ここで何が起きてるの?なぜ float なのか double に昇格しない ?正直なところ、コンパイラの考え方は異なります。その方法を説明させてください。

  • 可能であれば、コンパイラは関数の引数からテンプレートの引数を推測します。この場合、可能です。
  • コンパイラは、関数の引数ごとにこのテンプレート引数推定のプロセスを実行します。
  • 10.5f の場合 コンパイラは float を推測します T, の場合 for 5.5 コンパイラは double を推測します T の場合 .
  • もちろん、T を float にすることはできません と double 同時に。このあいまいさのために、コンパイルは失敗しました。

2 番目の免責事項:テンプレート引数推定のプロセスを簡略化しました。関数テンプレートとクラス テンプレートのテンプレート引数推定については、今後の投稿で追加の投稿を書く予定です。

もちろん、異なる型の値を比較したい.

2 つの型パラメータ

解決策は簡単なようです。 2 番目の型パラメーターを導入するだけです。

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

int main() {
 
 max(10.5f, 5.5);
 
}

かんたんだよ!右?しかし、深刻な問題があります。 3 つのクエスチョン マークは戻り値の型として表示されますか?この問題は通常、関数テンプレートに複数の型パラメーターがある場合に発生します。戻り値の型は?.

この具体的なケースでは、戻り値の型は T、T2、または T と T2 から派生した Type R のいずれである必要がありますか?これは C++11 より前は難しい作業でしたが、C++11 では非常に簡単です。

私が考えているいくつかの解決策を次に示します。

// automaticReturnTypeDeduction.cpp

#include <type_traits>

template <typename T1, typename T2> // (1)
typename std::conditional<(sizeof(T1) > sizeof(T2)), T1, T2>::type max1(T1 lhs,T2 rhs) {
 return (lhs > rhs)? lhs : rhs;
}

template <typename T1, typename T2> // (2)
typename std::common_type<T1, T2>::type max2(T1 lhs,T2 rhs) {
 return (lhs > rhs)? lhs : rhs;
}

template <typename T1, typename T2> // (3)
auto max3(T1 lhs,T2 rhs) {
 return (lhs > rhs)? lhs : rhs;
}

int main() {
 
 max1(10.5f, 5.5); 
 max2(10.5f, 5.5); 
 max3(10.5f, 5.5); 
 
}

最初の 2 つのバージョン max1 (1 行目) と max2 (2 行目) は type-traits ライブラリに基づいています。 3 番目のバージョン max3 (3行目) auto の自動型推定を使用 .

  • max1 (1 行目):typename std::conditional<(sizeof(T1) > sizeof(T2)), T1, T2>::type タイプ T1を返します 、または T2 それはもっと大きいです。 std::conditional はコンパイル時の三項演算子の一種です。
  • max2 (line2): typename td::common_type<T1, T2>::type 型 T1 と T2 の共通型を返します。 std::common_type は、任意の数の引数を受け入れることができます。
  • max3 (line3):auto 自明であるべきです。
typename にイライラしているかもしれません 関数テンプレート max1 の戻り値の型の前 そしてmax2。 T1 と T2 は従属名です。従属名とは何ですか?従属名は基本的に、テンプレート パラメーターに依存する名前です。この場合、T1 と T2 が型であるというヒントをコンパイラに与える必要があります。基本的に、非型またはテンプレートにすることもできます。 3 番目の免責事項:依存型については別の投稿で書きます。 C++ Insights が提供するものを見てみましょう。テンプレートのインスタンス化のみを示します。プログラム全体を分析する場合は、次のリンクをたどってください:C++ Insights。
  • max1 (1 行目):戻り値の型を推測することしかできません。 return ステートメントでは、小さい方の型 (float) が double に変換されます。 .
  • max2 (2行目):max1 について 、 return ステートメントは戻り値の型についてのアイデアを提供します:float 値は double に変換されます .

  • max3 (3 行目):これで、戻り値の型を明示的に確認できます。 double です .

次は?

今回の記事では、複数の型パラメーターを使用して、さまざまな型の関数引数の課題を解決しました。次回は、別のアプローチを取り、テンプレート引数を明示的に指定します。