C++ Primer を読んだところ、関数テンプレートの特殊化は高度なトピックであると書かれていましたが、完全に迷っています。関数テンプレートの特殊化が重要で必要な理由を誰かが例を挙げてもらえますか?
関数テンプレートは部分的な特殊化をサポートしているのに、クラス テンプレートはサポートしていないのはなぜですか? 根底にあるロジックは何ですか?
C++ Primer を読んだところ、関数テンプレートの特殊化は高度なトピックであると書かれていましたが、完全に迷っています。関数テンプレートの特殊化が重要で必要な理由を誰かが例を挙げてもらえますか?
関数テンプレートは部分的な特殊化をサポートしているのに、クラス テンプレートはサポートしていないのはなぜですか? 根底にあるロジックは何ですか?
なぜ関数が部分的な特殊化をサポートしないのかという質問は、ここで答えることができます。以下のコードは、さまざまな特殊化を実装する方法を示しています。
template<typename T>
bool Less(T a, T b)
{
cout << "version 1 ";
return a < b;
}
// Function templates can't be partially specialized they can overload instead.
template<typename T>
bool Less(T* a, T* b)
{
cout << "version 2 ";
return *a < *b;
}
template<>
bool Less<>(const char* lhs, const char* rhs)
{
cout << "version 3 ";
return strcmp(lhs, rhs) < 0;
}
int a = 5, b = 6;
cout << Less<int>(a, b) << endl;
cout << Less<int>(&a, &b) << endl;
cout << Less("abc", "def") << endl;
例が思い浮かびません。あなたが尋ねて以来、ほぼ試しています。Jagannathが指摘したように、関数を特殊化するのではなく、関数をオーバーロードするか、特性クラス (特殊化、部分的に特殊化することもできます) を使用することが長年のアドバイスでした。
たとえば、2 つのアイテムを交換する必要がある場合は、オーバーロードに依存する方が適切です (予測可能性と拡張性が高くなります)。
template<class T>
void f() {
T a, b;
using std::swap; // brings std::swap into scope as "fallback"
swap(a, b); // unqualified call (no "std::") so ADL kicks in
// also look at boost::swap
}
そして、あなたのタイプのスワップをどのように書くか:
// the cleanest way to do it for a class template:
template<class T>
struct Ex1 {
friend void swap(Ex1& a, Ex1& b) { /* do stuff */ }
};
// you can certainly place it outside of the class instead---but in the
// same namespace as the class---if you have some coding convention
// against friends (which is common, but misguided, IMHO):
struct Ex2 {};
void swap(Ex2& a, Ex2& b) { /* do stuff */ }
どちらもArgument Dependent Lookup (ADL) を許可します。
stringify/str や repr (表現) などの他の関数も同様に非メンバーにすることができ、オーバーロードによって ADL を利用できます。
struct Ex3 {
friend std::string repr(Ex3 const&) { return "<Ex3 obj>"; }
};
std::string repr(bool b) { return b ? "true" : "false"; }
// possible fallback:
template<class T>
std::string repr(T const& v) {
std::ostringstream out;
out << v;
return out.str();
}
// but in this particular case, I'd remove the fallback and document that
// repr() must be overloaded appropriately before it can be used with a
// particular type; for other operations a default fallback makes sense
別の見方をすると、関数テンプレートが特定の実装のレジストリとして機能できればいいのですが、制限があるため (現在の C++ では、C++0x がここで何をもたらすか正確にはわかりません)、同様に機能しません。そのレジストリの目的のためのオーバーロードまたはクラス テンプレートとして。
便利ではあるが重要ではない用途が 1 つあります。それは、特定の特殊化を別のライブラリ (おそらく共有ライブラリ (.so または .dll)) に入れるように簡単に定義することです。これは、汎用テンプレートへの変更を最小限に抑える必要があるため便利ですが、私にはめったにないように思われ (実際には、私の経験では確かにめったにありません)、実装者はオーバーロードまたは完全に特化したクラスへの転送を引き続き使用できるため、重要ではありません。テンプレートの特殊化されていないメソッド。
関数テンプレートの特殊化が重要な理由を説明するために、std::swap
テンプレート関数について考えてみましょう。デフォルトでは、std::swap(x, y)
基本的に次のことを行います。
T temp = x;
x = y;
y = temp;
x
ただし、これには の余分なコピーを作成する必要があり、割り当てで追加のコピーを行う可能性があるため、非効率的である可能性があります。x
が大きい場合 (たとえば、std::vector
要素が多い の場合) 、これは特に悪いことです。さらに、上記の各行は失敗して例外をスローする可能性がx
ありy
、結果として、不適切で矛盾した状態になる可能性があります。
これに対処するために、多くのクラスは独自のswap
メソッド ( を含むstd::vector
) を提供し、代わりにポインターを内部データに交換します。これはより効率的であり、決して失敗しないことが保証されます。
しかしstd::swap(x, y)
、いくつかの型で使用できるが、他の型を呼び出す必要がある場合がありx.swap(y)
ます。これは紛らわしく、一般的で一貫した方法で 2 つのオブジェクトを交換することができないため、テンプレートにとっては良くありません。
ただし、特定の型で呼び出されたときに呼び出されるように特化std::swap
することができます。つまり、どこでも使用でき、(できれば) 適切に動作することを期待できます。x.swap(y)
std::swap
基本的には、一般的なケースでは一般的な方法で動作するが、特殊なケースでも処理できるテンプレートを作成できるという考え方です。特殊化が使用される1つの例はにありstd::vector
ます。 は、 1バイトではなく、要素ごとに1ビットのみを使用するように要素 std::vector<bool>
をパックする特殊化です。他のすべてのタイプの通常の動的配列のように機能します。bool
std::vector<T>
特殊化のより高度な使用法はメタプログラミングです。たとえば、テンプレートの特殊化を使用してコンパイル時に階乗を計算する方法の例(Wikipediaから)を次に示します。
template <int N>
struct Factorial
{
enum { value = N * Factorial<N - 1>::value };
};
template <>
struct Factorial<0>
{
enum { value = 1 };
};