为什么重载优先于ADL中的显式特化

时间:2015-01-22 00:16:57

标签: c++ templates c++11 overloading argument-dependent-lookup

考虑代码:

#include <iostream>
#include <algorithm> // std::swap C++98
#include <utility> // std::swap C++11

namespace A
{
template<typename T>
struct Foo {};

template<typename T>
void swap(Foo<T> &lhs, Foo<T> &rhs)
{
    std::cout << "A::swap<T>" << std::endl;
}

} /* end namespace A */

namespace std // we explicitly specialize std::swap here
{

template<> // explicit template specialization for std::swap<int>
void swap(A::Foo<int> &lhs, A::Foo<int> &rhs) 
noexcept 
(is_nothrow_move_constructible<A::Foo<int>>::value && is_nothrow_move_assignable<A::Foo<int>>::value)
{
    std::cout << "std::swap<int>" << std::endl;
} 

} /* end namespace std */

int main()
{
    using std::swap;
    A::Foo<int> a, b; 
    A::Foo<double> x, y;

    swap(a, b); // ADL, expected to call std::swap<Foo<int>>, but NO
    swap(x, y); // ADL, expected to call A::swap<T>, YES
}

我希望std::swap显式专精化能成为调用swap(a, b)中更好的候选者,但似乎总是首选重载 A::swap,即输出是:

A::swap<T>
A::swap<T>

任何人都可以解释为什么这种行为?

3 个答案:

答案 0 :(得分:8)

功能模板显式专业化不参与重载决策。仅考虑从主模板合成的函数声明。如果通过重载决策过程选择一个这样的函数作为最佳可行函数,则在适当的情况下将使用相应主模板的显式特化。

在这种情况下,重载解析需要在从swap函数模板重载合成的函数声明foo<T>&和从std::swap主模板合成的函数声明{{1}之间进行选择。 }。

这两个函数都不能基于转换选择其他函数(它们具有相同的函数参数),两者都是模板特化,因此会考虑函数模板的部分排序,从而生成T&函数模板作为更专业的,所以从swap重载合成的函数声明获胜。

答案 1 :(得分:7)

显式函数模板特化永远不会改变调用哪个函数模板或重载,只调用模板的实现。

重载决策忽略了特化(与重载相反,对于不熟悉C ++函数模板怪癖的人来说,这看起来很像部分特化)。

我可以想象为什么:混合重载和模板特化选择规则会使规则更难以遵循并且正确。

通常,专门化函数模板很少是个好主意:重载或调度到模板类通常会更好。

请注意,该语言在重载决策中讨论“更专业化”:不要将此与“模板专业化”混淆:它们是不同的概念,不幸地共享一个词。

答案 2 :(得分:2)

如其他答案所述,原因是签名的 显式函数专业化不参与过载 分辨率。

显式特化的一个简单经验法则是,它们会更改在使用特定模板参数调用主模板时将使用的定义。

template <typename T> void foo (T) {
  // definition used for 'foo' when T is not 'int'
}

template <> void foo<int> (int) {
  // definition used for 'foo' when T is 'int'
}

选择最佳swap时,编译器会执行以下步骤(为简洁起见,我会忽略异常规范):

namespace A
{
  template <typename T> struct Foo { };

  template <typename T> void swap (Foo<T> &, Foo<T> &);
}

namespace std
{
  // swap provided by the STL
  template <typename T> void swap (T &, T &);

  // explicit specialization of std::swap
  template <> void swap (Foo<int> &, Foo<intT> &) { }
}

swaps生成主要模板的专业化:

  

13.3.1p7:在候选人是功能模板的每种情况下,   候选函数模板特化是使用生成的   模板参数推导(14.8.3,14.8.2)。那些候选人是   然后以通常的方式处理候选函数。

NB:明确的专业化不属于此。

对于swap(a,b),重载解析使用的候选集将会 包含以下生成的函数模板特化:

A::swap(Foo<int>&, Foo<int>)&);
std::swap(Foo<int>&, Foo<int>)&);

两者都是生成的模板特化,并且两者都具有完全相同的参数转换序列,因此要确定在最佳可行功能中使用以下项目符号的模板:

  

13.3.3p1b7:F1和F2是功能模板专业化,而且是   F1的功能模板比模板更专业   F2根据14.5.6.2中描述的部分排序规则。

部分排序归结为比较函数参数列表以找到更专业的。在此示例中,Foo<T>T更专业,因此swap(Foo<T>&, Foo<T>&)被认为是比swap(T&,T&)更好的匹配。结果是选择了A::swap,因此忽略了std::swap的显式特化。

如果代码已更改,因此显式专门化适用于A::swap而不是std::swap,那么当A::swap赢得重载解析时,将使用显式专门化:

namespace A
{
  template <typename T> struct Foo { };

  template <typename T> void swap (Foo<T> &, Foo<T> &);

  // explicit specialization of A::swap
  template <> void swap (Foo<int> &, Foo<intT> &) { }
}