模板专业化VS函数重载

Joh*_*0te 50 c++ stl overloading standard-library template-specialization

我注意到一本教科书,您可以为标准库函数提供自己的实现,例如swap(x,y)通过函数重载的模板特化.这对于可以从赋值交换以外的其他东西中受益的任何类型都是有用的,STL containers例如(我已经知道了已经写过的掉期).

我的问题是:

  1. 什么更好:模板专业化为您的专业交换实现,或函数重载提供您希望在没有模板的情况下使用的确切参数?

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

Pet*_*der 71

简短的故事:当你可以超载时,专注于你需要的时候.

长篇大论: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*);
Run Code Online (Sandbox Code Playgroud)

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

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*) !!!
Run Code Online (Sandbox Code Playgroud)

编译器在查看特化之前会执行重载解析.因此,在这两种情况下,重载决策都会选择foo(T*).但是,只有在第一种情况下它才能找到,foo<int*>(int*)因为在第二种情况下,int*专业化是专业化foo(T),而不是foo(T*).


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

该标准表示您可以向std命名空间添加特化.很好,所以你有一些Foo类型,它有一个高性能交换,然后你只专注swap(Foo&, Foo&)std命名空间.没问题.

但是,如果Foo是模板类呢?C++没有部分特殊功能,所以你不能专门化swap.您唯一的选择是重载,但标准规定您不允许在std命名空间中添加重载!

此时您有两种选择:

  1. swap(Foo<T>&, Foo<T>&)在您自己的命名空间中创建一个函数,并希望通过ADL找到它.我说"希望",因为如果标准库调用交换,std::swap(a, b);那么ADL根本不起作用.

  2. 忽略标准中没有添加重载的部分,无论如何都要这样做.老实说,尽管技术上不允许,但在所有现实场景中它都会起作用.

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

  • "我说希望是因为"......几年前在WG21中就提到了这一点.所有标准库实现者都不知道. (6认同)
  • “现在让我们交换最后两个”。最后两行不一样。一种是“void foo&lt;int&gt;(int*);” 另一个是“void foo&lt;int*&gt;(int*);” (3认同)
  • 为什么这么多票?我在gcc 4.8和LLVM 5.0中测试过,第一个例子选择了foo(T*). (2认同)

Pao*_*o M 13

彼得亚历山大的答案几乎没有什么可补充的.让我简单地提一下使用函数的特殊化可能比重载更具优势:如果必须在没有参数的函数中进行选择.

例如

template<class T> T zero();
template<> int zero() { return 0; }
template<> long zero() { return 0L; }
Run Code Online (Sandbox Code Playgroud)

要使用函数重载执行类似的操作,您必须在函数签名中添加一个参数:

int zero(int) { return 0; }
long zero(long) { return 0L; }
Run Code Online (Sandbox Code Playgroud)


Mar*_*k B 5

您不允许重std命名命名空间中的函数,但您可以专门化模板(我记得),这是一个选项.

另一种选择是将您的swap函数放在与其操作的事物相同的命名空间中,然后using std::swap;再调用非限定交换.