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


91

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

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;
}

クラスの特殊化のアナロジーを探してください。それがクラスの特殊化と呼ばれる場合、なぜ関数に対してオーバーロードと同じことを考慮する必要があるのですか?
ナレク2011年

1
いいえ、特殊化の構文は異なります。以下の私の答えの(想定される)関数特殊化構文を見てください。
iammilind 2011年

2
なぜこれは「maxへの呼び出しはあいまいです」エラーをスローしないのですか?どのようにmax(5,5)解決しmax(T const&, T const&) [with T=int]、解決しませんmax(T1 const&, T2 const&) [with T1=int and T2=int]か?
NHDaly 2015

回答:


83

関数の部分的な特殊化は、標準ではまだ許可されていません。この例では、実際にはオーバーロードしており、max<T1,T2>関数を特殊化しいない
その構文は見ている必要があり、ややそれが許されていた、以下のように:

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

関数テンプレートの場合、コンパイラ拡張を除いて、C ++標準では完全な特殊化のみが許可されています。


1
@Narek、部分関数の特殊化は標準の一部ではありません(理由は何であれ)。MSVCはそれを拡張機能としてサポートしていると思います。しばらくすると、他のコンパイラでも許可される可能性があります。
iammilind 2011年

1
@iammilind:問題ありません。彼はすでにそれを知っているようです。そのため、彼は関数テンプレートでもそれを試みています。それで、私はそれをもう一度編集して、今それを明確にしました。
ナワズ2011年

22
部分的な特殊化が許可されない理由を説明できる人はいますか?
HelloGoodbye 2015

2
@ NHDaly、1つの関数が他の関数よりも一致しているため、あいまいさのエラーは発生しません。それは選択理由(T, T)オーバー(T1, T2)のために(int, int)前者保証は2つのパラメータがあること、および両方のタイプが同じであるため、です。後者は、2つのパラメーターがあることを保証するだけです。コンパイラは常に正確な説明を選択します。たとえば、「川」の2つの説明から選択する必要がある場合、どちらを選択しますか?「水の収集」対「流れる水の収集」。
iammilind 2015年

1
@kfsone、この機能はレビュー中であるため、解釈の余地があると思います。このopen-stdセクションを参照してください。これは、C ++標準で関数テンプレートの部分的な特殊化が許可されていないの
iammilind 2018

44

他の回答が指摘しているように、部分的な特殊化は許可されていないため、以下のように、を使用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

編集:残っている他のすべてのケースを処理できるようにする必要がある場合は、すでに処理されたケースが一致してはならないことを示す定義を追加できます。そうしないと、あいまいな定義に陥ってしまいます。定義は次のようになります。

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

このすべてのケースは少し退屈に見えますが、コンパイラにすでに行ったことをすべて伝える必要があるため、最大5つまたはそれ以上の特殊化を処理することは非常に実行可能です。


これは、関数のオーバーロードによってはるかに単純で明確な方法で処理できるため、実際にはこれを行う必要はありません。
エイドリアン

2
@Adrianこれを解決するための他の関数オーバーロードアプローチは本当に考えられません。部分的なオーバーロードが許可されていないことに気づきましたか?あなたがそれがより明確であると思うならば、あなたの解決策を私たちと共有してください。
ルーベンス

1
テンプレート化されたすべての関数を簡単にキャッチする他の方法はありますか?
ニック

15

専門とは何ですか?

テンプレートを本当に理解したい場合は、関数型言語を確認する必要があります。C ++のテンプレートの世界は、それ自体が純粋に関数型のサブ言語です。

関数型言語では、選択はパターンマッチングを使用して行われます。

-- 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

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

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

  1. 通常の関数と特殊化されていないテンプレートの間で過負荷の解決を実行します
  2. 特殊化されていないテンプレートが選択されている場合は、より適切な特殊化が存在するかどうかを確認します

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

そのため、関数のテンプレートの特殊化は、(文字通り)第2ゾーンの市民です。私に関する限り、それらがない方が良いでしょう。代わりにオーバーロードを使用してテンプレートの特殊化の使用を解決できないケースはまだ発生していません。

これはテンプレートの特殊化ですか?

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


"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."非型テンプレートパラメータはどうですか?
ジュールGM

@Julius:などのダミーパラメータを導入しても、オーバーロードを使用できますboost::mpl::integral_c<unsigned, 3u>。別の解決策として、enable_if/を使用することもできますがdisable_if、話は異なります。
Matthieu M.

8

非クラス、非変数の部分特殊化は許可されていませんが、次のように述べています。

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

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

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, "");

4

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

オーバーロードと部分的な特殊化は、場合によっては同じ効果をもたらす可能性がありますが、すべてではありません。


4
そのためswap、名前空間にオーバーロードを配置します。
jpalecek 2011年

2

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

想像してみましょう、これが私たちが解決しようとしたことです:

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?
}

これ、代替案(専門分野ではなく通常のオーバーロード、ルーベンスによって提案された回避策、... –これらが悪い、または私の方が優れているということではなく、別の1つだけ)が非常に多くの共通コードを共有する場合に特に興味深い可能性があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.