前回の投稿テンプレートの特殊化からわかるように、関数テンプレートは完全にしか特殊化できませんが、部分的に特殊化することはできません。長い話を簡単に言うと、関数テンプレートを特殊化しないでください。関数のオーバーロードを使用するだけです。
なぜ私が C++ の機能について書いているのか疑問に思われるかもしれませんが、使用すべきではありません。理由は非常に簡単です。完全に特殊化された関数テンプレートの驚くべき動作が見られた場合は、代わりに非ジェネリック関数を使用することをお勧めします.
関数テンプレートを特殊化しない
タイトルで思い出すかも?右。これは C++ コア ガイドラインからのタイトルです:T.144:関数テンプレートを特殊化しない
ルールの理由は非常に短く、関数テンプレートの特殊化はオーバーロードに参加しません。それが何を意味するか見てみましょう。私のプログラムは、Dimov/Abrahams のプログラム スニペットに基づいています。
// dimovAbrahams.cpp #include <iostream> #include <string> // getTypeName template<typename T> // (1) primary template std::string getTypeName(T){ return "unknown"; } template<typename T> // (2) primary template that overloads (1) std::string getTypeName(T*){ return "pointer"; } template<> // (3) explicit specialization of (2) std::string getTypeName(int*){ return "int pointer"; } // getTypeName2 template<typename T> // (4) primary template std::string getTypeName2(T){ return "unknown"; } template<> // (5) explicit specialization of (4) std::string getTypeName2(int*){ return "int pointer"; } template<typename T> // (6) primary template that overloads (4) std::string getTypeName2(T*){ return "pointer"; } int main(){ std::cout << '\n'; int* p; std::cout << "getTypeName(p): " << getTypeName(p) << '\n'; std::cout << "getTypeName2(p): " << getTypeName2(p) << '\n'; std::cout << '\n'; }
確かに、コードは非常に退屈に見えますが、我慢してください。プライマリ テンプレート getTypeName をインライン (1) で定義しました。 (2) はポインターのオーバーロードであり、(3) int
の完全な特殊化です。 ポインター。 getTypeName2 の場合は、小さなバリエーションを作成しました。明示的な specialisation
を入れました (5) ポインターのオーバーロードの前 (6)。
この並べ替えは驚くべき結果をもたらします。
最初のケースでは int ポインターの完全な特殊化が呼び出され、2 番目のケースではポインターのオーバーロードが呼び出されます。何?この非直感的な動作の理由は、オーバーロードの解決が関数テンプレートの特殊化を無視するためです。オーバーロードの解決は、プライマリ テンプレートと関数で動作します。どちらの場合も、オーバーロードの解決により、両方のプライマリ テンプレートが見つかりました。最初のケース (getTypeName) では、ポインター バリアントの方が適しているため、int ポインターの明示的な特殊化が選択されました。 2 番目のバリアント (getTypeName2) では、ポインター バリアントが選択されていますが、完全な特殊化はプライマリ テンプレートに属しています (4 行目)。その結果、無視されました。
これはかなり複雑でした。次のルールに注意してください: 関数テンプレートを特殊化せず、代わりに非汎用関数を使用してください。
私の発言の証拠が欲しいですか?ここにあります:(3) と (5) の非ジェネリック関数の明示的な特殊化を作成すると、問題が解決します。テンプレート宣言 template<>
をコメントアウトするだけです .簡単にするために、他のコメントは削除しました。
// dimovAbrahams.cpp #include <iostream> #include <string> // getTypeName template<typename T> std::string getTypeName(T){ return "unknown"; } template<typename T> std::string getTypeName(T*){ return "pointer"; }
// template<> // (3) std::string getTypeName(int*){ return "int pointer"; } // getTypeName2 template<typename T> std::string getTypeName2(T){ return "unknown"; }
// template<> // (5) std::string getTypeName2(int*){ return "int pointer"; } template<typename T> std::string getTypeName2(T*){ return "pointer"; } int main(){ std::cout << '\n'; int* p; std::cout << "getTypeName(p): " << getTypeName(p) << '\n'; std::cout << "getTypeName2(p): " << getTypeName2(p) << '\n'; std::cout << '\n'; }
現在、関数のオーバーロードは期待どおりに機能し、非ジェネリック関数は int
を受け取ります ポインターが使用されます。
テンプレート引数についてはすでに書きました。しかし、一つ重要な事実を忘れていました。関数テンプレートとクラス テンプレートにデフォルトのテンプレート引数を指定できます。
デフォルトのテンプレート引数
標準テンプレート ライブラリ (STL) のクラス テンプレートに共通するものは何ですか?はい!テンプレート引数の多くにはデフォルトがあります。
以下にいくつかの例を示します。
template< typename T, typename Allocator = std::allocator<T> > class vector; template< typename Key, typename T, typename Hash = std::hash<Key>, typename KeyEqual = std::equal_to<Key>, typename Allocator = std::allocator< std::pair<const Key, T>> > class unordered_map; template< typename T, typename Allocator = std::allocator<T> > class deque; template< typename T, typename Container = std::deque<T> > class stack; template< typename CharT, typename Traits = std::char_traits<CharT>, typename Allocator = std::allocator<CharT> > class basic_string;
これは STL の機能の一部です:
- 各コンテナには、その要素に依存するデフォルトのアロケータがあります。
std::unordered_map: std::unordered_map<std::string, int>.
のキー タイプや値タイプなど、必要な引数を指定する必要があります。
<リ> - std::string は、一般的な文字型の単なるエイリアスです。
std::basic_string.
に基づくエイリアスは次のとおりです。
std::unordered_map
をインスタンス化できます キーの has 値を返す特別なハッシュ関数と、2 つのキーが等しいかどうかを判断する特別なバイナリ述語を使用する:std::unordered_map<std::string, int, MyHash>
、または std::unordered_map<std::string, int, MyHash, MyBinaryPredicate>
.
std::string std::basic_string<char> std::wstring std::basic_string<wchar_t> std::u8string std::basic_string<char8_t> (C++20) std::u16string std::basic_string<char16_t> (C++11) std::u32string std::basic_string<char32_t> (C++11)
もちろん、テンプレート引数にデフォルトがある場合、次のテンプレート引数にもデフォルトが必要です。
これまでは、クラス テンプレートの既定のテンプレート引数についてのみ説明してきました。関数テンプレートに関する例を挙げて、この記事を締めくくりたいと思います。
同じタイプのいくつかのオブジェクトのうち、どちらが小さいかを判断したいとします。 isSmaller
などのアルゴリズム 一般的なアイデアをモデル化しているため、テンプレートにする必要があります。
// templateDefaultArguments.cpp #include <functional> #include <iostream> #include <string> class Account{ public: explicit Account(double b): balance(b){} double getBalance() const { return balance; } private: double balance; }; template <typename T, typename Pred = std::less<T>> // (1) bool isSmaller(T fir, T sec, Pred pred = Pred() ){ return pred(fir,sec); } int main(){ std::cout << std::boolalpha << '\n'; std::cout << "isSmaller(3,4): " << isSmaller(3,4) << '\n'; // (2) std::cout << "isSmaller(2.14,3.14): " << isSmaller(2.14,3.14) << '\n'; std::cout << "isSmaller(std::string(abc),std::string(def)): " << isSmaller(std::string("abc"),std::string("def")) << '\n'; bool resAcc= isSmaller(Account(100.0),Account(200.0), // (3) [](const Account& fir, const Account& sec){ return fir.getBalance() < sec.getBalance(); }); std::cout << "isSmaller(Account(100.0),Account(200.0)): " << resAcc << '\n'; bool acc= isSmaller(std::string("3.14"),std::string("2.14"), // (4) [](const std::string& fir, const std::string& sec){ return std::stod(fir) < std::stod(sec); }); std::cout << "isSmaller(std::string(3.14),std::string(2.14)): " << acc << '\n'; std::cout << '\n'; }
デフォルトのケース (2) では、 isSmaller
期待どおりに動作します。 isSmaller
(1) テンプレート引数 std::less
を使用 これは、STL で定義済みの多くの関数オブジェクトの 1 つです。小なり演算子 <
を適用します その引数に。これを使用するには、次の行で std::less Pred pred = Pred()
.
デフォルトのテンプレート引数のおかげで、アカウント (3) または文字列 (4) を比較できます。 Account
より小さい演算子はサポートされていません。それにもかかわらず、私は Account
を比較できます 秒。 (3)。さらに、辞書順ではなく内部番号 (4) に基づいて文字列を比較したいと考えています。 (3) と (4) の 2 つのラムダ式を 2 項述語として提供することで、仕事をうまくこなすことができました。
次は?
この投稿の冒頭にある図をよく見てみると、テンプレートの基本について説明したことがお分かりいただけると思います。テンプレートに関する次回の投稿では、さらに詳細を掘り下げ、テンプレートのインスタンス化について書きます。