C ++函数模板部分专业化?


87

我知道下面的代码是类的部分专业化:

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

寻找类专业化的类比。如果它被称为类专门化,那为什么我应该在功能上考虑与重载相同的东西呢?
Narek

1
不,专业化语法不同。在下面的答案中查看(假设的)函数专门化语法。
iammilind

2
为什么这不会引发“致电至最大是模棱两可”的错误?如何max(5,5)解决max(T const&, T const&) [with T=int]而不是解决max(T1 const&, T2 const&) [with T1=int and T2=int]
NHDaly

Answers:


81

根据标准,尚不允许功能部分专门化。在该示例中,您实际上是在重载并且未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

1
@iammilind:没问题。他似乎已经知道这一点。这就是为什么他也尝试将其用于功能模板。因此,我再次对其进行了编辑,现在使其变得清晰。
Nawaz

19
谁能解释为什么不允许部分专业化?
HelloGoodbye 2015年

2
@NHDaly,它不会产生歧义错误,因为1个函数比另一个函数更好。为什么选择(T, T)(T1, T2)(int, int),是因为前者保证有2个参数,这两种类型都一样; 后者仅保证有2个参数。编译器始终选择准确的描述。例如,如果您必须在“河”的2个描述之间进行选择,您应该选择哪个?“水的收集”与“水的流动”。
iammilind

1
@kfsone,我认为此功能正在接受审核,因此可供解释。您可以参考open-std部分,我在为什么C ++标准不允许函数模板部分专业化中
iammilind

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我真的想不出任何其他函数重载方法来解决这个问题。您发现不允许部分重载,对吧?如果您认为更清晰,请与我们分享您的解决方案。
鲁本斯2015年

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

这样,功能的模板专业化(从字面上看)是第二区公民。就我而言,如果没有它们,我们会更好:我还没有遇到过无法通过重载解决模板专业化使用的情况。

这是模板专业化吗?

不,这只是过载,这很好。实际上,重载通常会按我们预期的那样工作,而专业化可能令人惊讶(请记住我链接的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."非类型模板参数怎么样?
Jules GM

@Julius:您仍然可以使用重载,尽管可以通过引入虚拟参数(例如)来实现boost::mpl::integral_c<unsigned, 3u>。另一个解决方案也可能是使用enable_if/ disable_if,尽管情况有所不同。
Matthieu M.

7

不允许使用非类,非变量的部分专业化,但是请注意:

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

添加一个类来转发函数调用可以解决此问题,下面是一个示例:

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

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

可能会很有趣,尤其是如果替代方法(正常的重载代替专业知识,由鲁本斯提出的解决方法,... –不是这些不好或我的更好,只是一种)会共享很多通用代码。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.