103

我知道下面的代码是一个类的部分特化:

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


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

我也知道 C++ 不允许函数模板部分特化(只允许完整)。但是我的代码是否意味着我已经为一个/相同类型的参数部分专门化了我的函数模板?因为它适用于 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;
}
4

6 回答 6

93

根据标准,功能部分特化是不允许的。在示例中,您实际上是在重载而不是专门化该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++ 标准只允许完全特化——不包括编译器扩展!

于 2011-11-09T07:13:20.303 回答
51

由于不允许部分专业化 - 正如其他答案所指出的那样 - 您可以使用std::is_sameand解决它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 个或更多的专业化是完全可行的。

于 2014-01-19T14:53:33.530 回答
17

什么是专业化?

如果你真的想了解模板,你应该看看函数式语言。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

因此,函数的模板专业化是第二区公民(字面意思)。就我而言,没有它们我们会更好:我还没有遇到过模板专业化使用无法通过重载来解决的情况。

这是模板专业化吗?

不,这只是一个过载,这很好。事实上,重载通常按照我们的预期工作,而专业化可能会令人惊讶(记住我链接的 GotW 文章)。

于 2011-11-09T07:49:12.843 回答
11

不允许非类、非变量的偏特化,但如前所述:

计算机科学中的所有问题都可以通过另一个层次的间接来解决。——大卫·惠勒

添加一个类来转发函数调用可以解决这个问题,这里是一个例子:

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, "");
于 2018-01-12T02:48:50.823 回答
4

不可以。例如,您可以合法地专门化std::swap,但不能合法地定义自己的重载。这意味着您无法std::swap为自己的自定义类模板工作。

在某些情况下,重载和部分特化可以产生相同的效果,但远非如此。

于 2011-11-09T07:09:19.870 回答
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);
}

好的,部分模板函数特化,我们不能这样做......所以让我们将特化所需的部分“导出”到辅助函数中,特化那个并使用它:

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

可能很有趣,特别是如果替代方案(正常重载而不是专门化,Rubens 提出的解决方法,...... - 不是这些不好或我的更好,只是另一个)将共享相当多的通用代码。

于 2017-09-22T08:23:16.057 回答