自動戻り型 (C++98)

使用されている C++ 標準に応じて、関数テンプレートの正しい戻り値の型を返すさまざまな方法があります。この投稿では、特性 (C++98) から始めて、次の投稿で C++11/14 に進み、概念 (C++20) で終わります。

これが今日の投稿の課題です。

template <typename T, typename T2>
??? sum(T t, T2 t2) {
 return t + t2;
}

sum などの関数テンプレートがある場合 少なくとも 2 つの型パラメーターを使用すると、一般に関数の戻り値の型を決定できません。もちろん、sum は算術演算 t + t2 の型を返す必要があります。 提供します。 std::type_info. で実行時型情報 (RTTI) を使用する例をいくつか示します。

// typeinfo.cpp

#include <iostream>
#include <typeinfo>
 
int main() {
 
 std::cout << '\n';
 
 std::cout << "typeid(5.5 + 5.5).name(): " << typeid(5.5 + 5.5).name() << '\n';
 std::cout << "typeid(5.5 + true).name(): " << typeid(5.5 + true).name() << '\n';
 std::cout << "typeid(true + 5.5).name(): " << typeid(true + 5.5).name() << '\n';
 std::cout << "typeid(true + false).name(): " << typeid(true + false).name() << '\n';

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

MSVC は GCC や Clang の人間が読める名前とは対照的に生成するため、MSVC を使用して Windows でプログラムを実行しました。

2 つの double を追加 s は double を返します 、 double を追加 そして bool bool を返します 、および 2 つの bool を追加します s は int を返します .

私の例では、算術型のみを使用しています。私の例を算術演算をサポートするユーザー定義に適用したい場合は、私のソリューションを拡張する必要があります。

さて、私の旅は C++98 から始まります。

C++98

正直なところ、C++98 には正しい型を返すための一般的な解決策はありません。基本的に、テンプレート トレイトとも呼ばれるトレイトと呼ばれる手法を使用して、型推定規則を実装する必要があります。特性クラスは、テンプレート パラメーターに関する有用な情報を提供し、テンプレート パラメーターの代わりに使用できます。

次のクラス ResultType 完全なテンプレートの特殊化を使用して型から型へのマッピングを提供します。

// traits.cpp

#include <iostream>
#include <typeinfo>

template <typename T, typename T2> // primary template (1)
struct ReturnType; 

template <> // full specialization for double, double
struct ReturnType <double, double> {
 typedef double Type;
};

template <> // full specialization for double, bool
struct ReturnType <double, bool> {
 typedef double Type; // (2)
};

template <> // full specialization for bool, double
struct ReturnType <bool, double> {
 typedef double Type;
};

template <> // full specialization for bool, bool
struct ReturnType <bool, bool> {
 typedef int Type;
};

template <typename T, typename T2> 
typename ReturnType<T, T2>::Type sum(T t, T2 t2) { // (3)
 return t + t2;
}

int main() {

 std::cout << '\n';

 std::cout << "typeid(sum(5.5, 5.5)).name(): " << typeid(sum(5.5, 5.5)).name() << '\n';
 std::cout << "typeid(sum(5.5, true)).name(): " << typeid(sum(5.5, true)).name() << '\n';
 std::cout << "typeid(sum(true, 5.5)).name(): " << typeid(sum(true, 5.5)).name() << '\n';
 std::cout << "typeid(sum(true, false)).name(): " << typeid(sum(true, false)).name() << '\n';

 std::cout << '\n';

}

行 (1) は、プライマリ テンプレートまたは一般的なテンプレートです。プライマリ テンプレートは、次の完全な特殊化の前に宣言する必要があります。プライマリ テンプレートが必要ない場合は、1 行目のような宣言で問題ありません。次の行は、<double, double> の完全な特殊化を提供します。 、 <double, bool> の場合 、 <bool, double> の場合 、および <bool, bool> の場合 .テンプレートの専門化の詳細については、以前の投稿で読むことができます:

  • テンプレートの専門化
  • テンプレートの専門化 - クラス テンプレートの詳細
  • 関数テンプレートの完全な特殊化

ReturnType のさまざまな完全な特殊化における重要な観察 それらはすべてエイリアス Type を持っているということです typedef double Type など (2行目)。このエイリアスは、関数テンプレート sum の戻り値の型です (3 行目):typename ReturnType<T, T2>::type .

特性は期待どおりに機能します。

なぜ typename を使用したのか疑問に思われるかもしれません 関数テンプレート sumの戻り型式で . Dependent Names に関する以前の投稿の少なくとも 1 人の読者 typename を適用するタイミングを尋ねられました または .template テンプレートに 簡単な答えは、コンパイラが式 ReturnType<T, T2>::Type かどうかを判断できないということです。 タイプ (この場合など)、非タイプ、またはテンプレートです。 typename の使用 ReturnType<T, T2>::Type より前 コンパイラに重要なヒントを与えます。長い回答は、私の以前の投稿 Dependent Names で読むことができます。

過負荷がありません

もともと、投稿を続けて C++11 について書きたかったのですが、追加の質問があると思います:関数テンプレート sum を呼び出すとどうなるか 部分的なテンプレートの特殊化が定義されていない引数を使用していますか? sum(5.5f, 5) で試してみましょう .

// traitsError.cpp

#include <iostream>
#include <typeinfo>

template <typename T, typename T2> // primary template
struct ReturnType; 

template <> // full specialization for double, double
struct ReturnType <double, double> {
 typedef double Type;
};

template <> // full specialization for double, bool
struct ReturnType <double, bool> {
 typedef double Type;
};

template <> // full specialization for bool, double
struct ReturnType <bool, double> {
 typedef double Type;
};

template <> // full specialization for bool, bool
struct ReturnType <bool, bool> {
 typedef int Type;
};

template <typename T, typename T2> 
typename ReturnType<T, T2>::Type sum(T t, T2 t2) {
 return t + t2;
}

int main() {

 std::cout << '\n';

 std::cout << "typeid(sum(5.5f, 5.5)).name(): " << typeid(sum(5.5f, 5.5)).name() << '\n';

 std::cout << '\n';

}

多くの C++ プログラマーは、float 値 5.5fdouble に変換されます <double, double> の完全な特殊化 使用されている。

いいえ!タイプは正確に一致する必要があります。 MSVC コンパイラは正確なエラー メッセージを表示します。オーバーロード sum はありません T = float の場合 そして T2 = double 利用可能。プライマリ テンプレートが定義されていないため、インスタンス化できません。

型は変換されず、値などの式のみが変換可能:double res  = 5.5f + 5.5;

デフォルトの戻り値の型

プライマリ テンプレートの宣言から定義を作成すると、プライマリ テンプレートがデフォルトのケースになります。したがって、次の ReturnType の実装 long double を使用 デフォルトの戻り型として。

// traitsDefault.cpp

#include <iostream>
#include <typeinfo>

template <typename T, typename T2> // primary template
struct ReturnType {
 typedef long double Type;  
};

template <> // full specialization for double, double
struct ReturnType <double, double> {
 typedef double Type;
};

template <> // full specialization for double, bool
struct ReturnType <double, bool> {
 typedef double Type;
};

template <> // full specialization for bool, double
struct ReturnType <bool, double> {
 typedef double Type;
};

template <> // full specialization for bool, bool
struct ReturnType <bool, bool> {
 typedef int Type;
};

template <typename T, typename T2> 
typename ReturnType<T, T2>::Type sum(T t, T2 t2) {
 return t + t2;
}

int main() {

 std::cout << '\n';

 std::cout << "typeid(sum(5.5, 5.5)).name(): " << typeid(sum(5.5, 5.5)).name() << '\n';
 std::cout << "typeid(sum(5.5, true)).name(): " << typeid(sum(5.5, true)).name() << '\n';
 std::cout << "typeid(sum(true, 5.5)).name(): " << typeid(sum(true, 5.5)).name() << '\n';
 std::cout << "typeid(sum(true, false)).name(): " << typeid(sum(true, false)).name() << '\n';
 std::cout << "typeid(sum(5.5f, 5.5)).name(): " << typeid(sum(5.5f, 5.5)).name() << '\n';

 std::cout << '\n';

}

sum(5.5f, 5.f) の呼び出し プライマリ テンプレートのインスタンス化を引き起こします。

次は?

C++11 では、戻り値の型を自動的に推測するさまざまな方法があります。 C++14 では、これらの手法に構文糖衣が追加されており、C++20 では非常に明示的に記述できるようになっています。次回の投稿で改善点について詳しくお読みください。