为什么在 ADL 中重载优先于显式特化
why is overload preferred to explicit specialization in ADL
考虑代码:
#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)
中是更好的候选者,但是 overload A::swap
始终是首选,即输出为:
A::swap<T>
A::swap<T>
谁能解释为什么会出现这种行为?
显式函数模板特化永远不会改变调用哪个函数模板或重载,只会改变调用模板时的实现。
重载解析忽略特化(与重载相反,对于不熟悉 C++ 函数模板怪癖的人来说,重载看起来很像部分特化)。
我可以想象为什么:混合重载和模板专业化选择规则会使规则更难遵循和正确。
一般来说,专门化函数模板很少是个好主意:重载或分派到模板 class 通常更好。
请注意,该语言在重载决策中谈论 'more specialized':不要将其与 'template specialization' 混淆:它们是不同的概念,不幸的是共享一个词。
函数模板显式特化不参与重载决策。只考虑从主模板合成的函数声明。如果一个这样的函数被重载决策过程选为最佳可行函数,则在合适的情况下将使用相应主模板的显式特化。
在这种情况下,重载决策需要在从采用 foo<T>&
的 swap
函数模板重载合成的函数声明和采用 std::swap
的主模板合成的函数声明之间进行选择13=].
None 可以根据转换选择这两个函数中的另一个(它们具有相同的函数参数),两者都是模板特化,因此考虑了函数模板的部分排序,这会产生您的 swap
函数模板 更专业 ,因此从 swap
重载合成的函数声明胜出。
其他回答里也提到了,原因是
显式函数特化不参与重载
分辨率。
显式特化的一个简单经验法则是,它们会更改使用特定模板参数调用主模板时将使用的定义。
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: In each case where a candidate is a function template,
candidate function template specializations are generated using
template argument deduction (14.8.3, 14.8.2). Those candidates are
then handled as candidate functions in the usual way.
注意: 显式专业化不是其中的一部分。
对于swap(a,b)
,重载决议使用的候选集将
包含以下生成的函数模板特化:
A::swap(Foo<int>&, Foo<int>)&);
std::swap(Foo<int>&, Foo<int>)&);
两者都是生成的模板特化,并且都具有完全相同的参数转换序列,因此要确定使用哪个模板,最佳可行函数中的以下项目符号说:
13.3.3p1b7: F1 and F2 are function template specializations, and the
function template for F1 is more specialized than the template for
F2 according to the partial ordering rules described in 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> &) { }
}
考虑代码:
#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)
中是更好的候选者,但是 overload A::swap
始终是首选,即输出为:
A::swap<T> A::swap<T>
谁能解释为什么会出现这种行为?
显式函数模板特化永远不会改变调用哪个函数模板或重载,只会改变调用模板时的实现。
重载解析忽略特化(与重载相反,对于不熟悉 C++ 函数模板怪癖的人来说,重载看起来很像部分特化)。
我可以想象为什么:混合重载和模板专业化选择规则会使规则更难遵循和正确。
一般来说,专门化函数模板很少是个好主意:重载或分派到模板 class 通常更好。
请注意,该语言在重载决策中谈论 'more specialized':不要将其与 'template specialization' 混淆:它们是不同的概念,不幸的是共享一个词。
函数模板显式特化不参与重载决策。只考虑从主模板合成的函数声明。如果一个这样的函数被重载决策过程选为最佳可行函数,则在合适的情况下将使用相应主模板的显式特化。
在这种情况下,重载决策需要在从采用 foo<T>&
的 swap
函数模板重载合成的函数声明和采用 std::swap
的主模板合成的函数声明之间进行选择13=].
None 可以根据转换选择这两个函数中的另一个(它们具有相同的函数参数),两者都是模板特化,因此考虑了函数模板的部分排序,这会产生您的 swap
函数模板 更专业 ,因此从 swap
重载合成的函数声明胜出。
其他回答里也提到了,原因是 显式函数特化不参与重载 分辨率。
显式特化的一个简单经验法则是,它们会更改使用特定模板参数调用主模板时将使用的定义。
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: In each case where a candidate is a function template, candidate function template specializations are generated using template argument deduction (14.8.3, 14.8.2). Those candidates are then handled as candidate functions in the usual way.
注意: 显式专业化不是其中的一部分。
对于swap(a,b)
,重载决议使用的候选集将
包含以下生成的函数模板特化:
A::swap(Foo<int>&, Foo<int>)&);
std::swap(Foo<int>&, Foo<int>)&);
两者都是生成的模板特化,并且都具有完全相同的参数转换序列,因此要确定使用哪个模板,最佳可行函数中的以下项目符号说:
13.3.3p1b7: F1 and F2 are function template specializations, and the function template for F1 is more specialized than the template for F2 according to the partial ordering rules described in 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> &) { }
}