web-dev-qa-db-ja.com

C ++関数テンプレートの部分的な専門化?

私は以下のコードがクラスの部分的な特殊化であることを知っています:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

また、C++では関数テンプレートの部分的な特殊化が許可されていないことも知っています(完全なもののみが許可されています)。しかし、私のコードは、関数テンプレートを1つ/同じ型の引数に部分的に特化したことを意味していますか? Microsoft Visual Studio 2010 Expressで機能するためです!いいえの場合、部分的な専門化の概念を説明してください。

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}
70
Narek

この例では、実際にはオーバーロード(特殊化ではない) the max<T1,T2> 関数。部分的な特殊化syntaxsomewhatのように見えるはずです(許可されていた):

//Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- specializing here
    return 10;
}

[注:関数テンプレートの場合、fullspecializationはC++標準で許可されています(コンパイラ拡張機能を除く)。]

70
iammilind

他の回答が指摘したように、部分的な特殊化は許可されていないため、std::is_sameおよびstd::enable_if、 以下のように:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

出力:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

編集:残っている他のすべてのケースを処理できるようにする必要がある場合は、既に処理されたケースはmatch-そうでなければ、あいまいな定義に陥ります。定義は次のとおりです。

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

生成するもの:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

このall-casesのことは少し退屈に見えますが、すでに行ったことをすべてコンパイラに伝える必要があるため、 5つ以上の専門分野。

35
Rubens

専門化とは?

テンプレートを本当に理解したい場合は、関数型言語をご覧ください。 C++のテンプレートの世界は、純粋に機能的な独自のサブ言語です。

関数型言語では、選択はPattern Matchingを使用して行われます。

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

ご覧のとおり、isJustの定義をoverloadします。

C++クラステンプレートはまったく同じように機能します。パラメーターの数と性質を示すmain宣言を提供します。それは単なる宣言であるか、定義(選択)としても機能し、(必要に応じて)パターンの特殊化を提供し、クラスの別の(そうでなければ愚かな)バージョンに関連付けることができます。

テンプレート関数の場合、特殊化はやや厄介です。それはオーバーロード解決といくらか矛盾します。そのため、特化は非特化バージョンに関連し、過負荷の解決中に特化は考慮されないことが決定されました。したがって、適切な関数を選択するためのアルゴリズムは次のようになります。

  1. 通常の機能と特殊化されていないテンプレート間でオーバーロード解決を実行します
  2. 特殊化されていないテンプレートが選択されている場合、より一致する特殊化が存在するかどうかを確認します

(詳細な処理については、 GotW#49 を参照)

そのため、関数のテンプレート特化は、セカンドゾーンの市民です(文字通り)。私が懸念している限り、それらがなくても良いでしょう。テンプレートの特殊化の使用がオーバーロードでは解決できない場合はまだありません。

これはテンプレートの専門ですか?

いいえ、それは単に過負荷であり、これは問題ありません。実際、通常、オーバーロードは期待どおりに機能しますが、特殊化は驚くべきことです(リンクしたGotWの記事を思い出してください)。

13
Matthieu M.

いいえ。たとえば、std::swap、ただし、独自のオーバーロードを法的に定義することはできません。つまり、std::swap独自のカスタムクラステンプレートで動作します。

オーバーロードと部分的な特殊化は、場合によっては同じ効果がありますが、すべてとはほど遠い場合があります。

4
Puppy

非クラス、非変数の部分的な特殊化は許可されていませんが、前述のとおり:

コンピュータサイエンスのすべての問題は、別のレベルの間接参照によって解決できます。 -デヴィッド・ホイーラー

クラスを追加して関数呼び出しを転送すると、これを解決できます。例を次に示します。

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");
3
user2709407

遅い答えですが、一部の遅い読者はそれを役に立つと思うかもしれません:時には、ヘルパー関数-特殊化できるように設計されている-が問題を解決することもできます。

だから、想像してみましょう、これが私たちが解決しようとした

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

OK、部分的なテンプレート関数の特殊化、それはできません...それでは、特殊化に必要な部分をヘルパー関数に「エクスポート」して、それを特殊化して使用しましょう。

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

このは、特に代替手段(特殊化の代わりに通常のオーバーロード、 回避策 ルーベンスによって提案された場合、...これらが悪いことや私のほうが良いということではなく、ちょうどanotherone)はかなり多くの共通コードを共有します。

2
Aconcagua