40

教材我注意到,您可以通过模板专门化为函数重载提供您自己的标准库函数实现,如swap(x,y)。这对任何可以从除赋值转换外的其他类型受益的类型都很有用,例如STL containers(已知有交换,我知道)。模板专精VS函数重载

我的问题是:

  1. 更棒的是:模板专门给你的专业 交换实现,或者函数重载提供你希望没有模板的确切 参数?

  2. 为什么它更好?或者如果他们是平等的,为什么呢?

回答

60

小故事:当你可以超载时,专门在你需要的时候。长篇小说:C++对待专业化和重载的方式非常不同。这可以用一个例子来解释。

template <typename T> void foo(T); 
template <typename T> void foo(T*); // overload of foo(T) 
template <>   void foo<int>(int*); // specialisation of foo(T*) 

foo(new int); // calls foo<int>(int*); 

现在让我们换掉最后两个。

template <typename T> void foo(T); 
template <>   void foo<int*>(int*); // specialisation of foo(T) 
template <typename T> void foo(T*); // overload of foo(T) 

foo(new int); // calls foo(T*) !!! 

编译器在重载分辨率之前甚至查看专业化。所以,在这两种情况下,重载解决方案都会选择foo(T*)。但是,只有在第一种情况下才会发现foo<int*>(int*),因为在第二种情况下,int*专业化是foo(T)的专门化,而不是foo(T*)


您提到过std::swap。这使事情变得更加复杂。

该标准说您可以将专门化添加到std命名空间。太棒了,所以你有一些Foo类型,它有一个高性能的交换,那么你只需在std命名空间中专门设置swap(Foo&, Foo&)。没问题。

但是,如果Foo是模板类呢? C++没有部分功能的专业化,所以你不能专注于swap。你唯一的选择是超载,但标准说你不允许在std命名空间中添加重载!

你必须在这一点上两个选项:

  1. 创建自己的命名空间的swap(Foo<T>&, Foo<T>&)功能,并希望它得到通过ADL找到。我说“希望”,因为如果标准库调用像std::swap(a, b);那样的交换,那么ADL根本无法工作。

  2. 忽略标准中说不添加重载的部分,并且无论如何都要这样做。老实说,即使它在技术上是不允许的,但在所有现实的情况下,它都会起作用。

但要记住的一件事是,不能保证标准库完全可以使用swap。大多数算法使用std::iter_swap,在我看过的一些实现中,它并不总是转发到std::swap

+0

很好的例子先生 – tenfour

+6

“我说希望,因为” ...这一点指出在几年前的WG21中。所有标准库实现者都不知道。 – MSalters

+0

精彩的回答,非常感谢。 –

5

您不允许在std命名空间中重载函数,但允许您专门化模板(我记得),所以这是一种选择。

另一种选择是在调用非限定交换之前,将swap函数放在与其运行的函数相同的名称空间中,并将其与using std::swap;相同。

+0

我更喜欢'使用std :: swap;'来'使用名称空间std;'。 –

+0

您可能会专注于某些模板:“程序可能会将任何标准库模板的模板专用化添加到名称空间std中,只有当声明依赖于用户定义的类型并且专业化符合原始模板的标准库要求而不是明确禁止。“_ [n3337; 17.6.4.2.1/1] 因此,您可以针对不在'std'中的类型专门化'std :: swap'。 – boycy

+0

为什么你不能重载std函数?是坏习惯,但std命名空间并不特殊。我不喜欢它,但许多产品为std命名空间中的类型添加std swap。 – Nick

10

彼得亚历山大的答案几乎没有添加。让我仅仅提到一个使用这个功能的专业化可能会优于超载:如果您必须在没有参数的函数中选择

例如

template<class T> T zero(); 
template<> int zero() { return 0; } 
template<> long zero() { return 0L; } 

要使用函数重载做同样的事情,你将有一个参数添加到函数签名:

int zero(int) { return 0; } 
long zero(long) { return 0L; } 
+1

有趣的用法:) –