【问题标题】:why is overload preferred to explicit specialization in ADL为什么重载优先于 ADL 中的显式专业化
【发布时间】:2015-01-22 00:16:57
【问题描述】:

考虑代码:

#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>

谁能解释为什么会出现这种行为?

【问题讨论】:

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


【解决方案1】:

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

在这种情况下,重载决策需要在从 swap 函数模板重载合成的函数声明和采用 T&amp;std::swap 主模板合成的函数声明之间进行选择。

这两个函数中的任何一个都不能根据转换来选择(它们具有相同的函数参数),它们都是模板特化,因此考虑了函数模板的部分排序,这会产生您的 swap 函数模板为 更专业,因此从您的swap 重载合成的函数声明获胜。

【讨论】:

  • 我同意术语 specialization 在这里令人困惑。如果使用“从主模板合成”的函数声明或显式专业化的声明(显式规范的声明不能与主模板不同),我认为这无关紧要。正如您在第三段中所说,重载集中两个函数的参数类型是相等的;在这一点上,它们是由不同的模式(foo&lt;T&gt;&amp;T&amp;)形成的并不重要。关键:最终的部分排序依赖于 primary 模板,而不是显式的特化。
  • @dyp 我明白你的意思 - 一个有趣的思路,我认为它同样正确。我猜如果在过程描述中甚至提到了明确的专业化,标准并没有遵循这条路径来避免“人们得到任何想法”的可能性。在考虑这些事情时,我通常会尽可能地遵循标准的逻辑,以“进入编译器的脑海”(假设 it 遵循标准......)。
  • @dyp 中间一个,关于函数声明来自哪里的相关性。最后一个当然是至关重要的,正如你所说,但它被称为“函数模板的部分排序”毕竟是有原因的 - 显式特化不再是模板,所以这部分应该是很清楚。
【解决方案2】:

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

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

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

一般来说,特化函数模板很少是一个好主意:重载或分派到模板类通常更好。

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

【讨论】:

    【解决方案3】:

    正如其他答案中提到的,原因是 显式函数特化不参与重载 分辨率。

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

    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)。这些候选人是 然后以通常的方式作为候选函数处理。

    注意:显式特化不是其中的一部分。

    对于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&lt;T&gt;T 更专业,因此 swap(Foo&lt;T&gt;&amp;, Foo&lt;T&gt;&amp;) 被认为比 swap(T&amp;,T&amp;) 更匹配。结果是选择了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> &) { }
    }
    

    【讨论】:

    • 作为我日常工作的一部分,我们为“现代 C++”制定了一套规则,这实际上是规则之一 (14.2.2)。有兴趣的朋友可以到我的个人资料网站链接到规则,可以免费在线浏览。
    猜你喜欢
    • 1970-01-01
    • 2020-07-18
    • 1970-01-01
    • 2016-08-07
    • 2017-04-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多