【问题标题】:C++ template functions priorityC++ 模板函数优先级
【发布时间】:2023-04-09 14:25:01
【问题描述】:
#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, const T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    double g = 2.;
    foo(a, g); // prints "first"

    return 0;
}

调用第二个foo重载,编译器只需要执行一次模板类型推导,但对于第一次重载,它需要执行两次。你能解释一下为什么调用第一个重载吗?

【问题讨论】:

  • 你不会做同样的事情吗?它的行为符合我的预期......

标签: c++ function templates overload-resolution


【解决方案1】:

重载解决方案分多个步骤完成。

首先,通过名称查找,我们选择可行的候选人列表。在这种情况下,即:

template <class U, class T>
void foo(U&, T&);            // with U = int, T = double

template <class T>
void foo(int&, const T&)     // with T = double

接下来,我们确定每个可行候选者的每个参数所需的转换顺序。这是[over.ics.rank]:

标准转换序列S1是比标准转换序列更好的转换序列 S2 如果 [...]

  • S1 是 S2 的适当子序列(比较规范形式的转换序列 由 13.3.3.1.1 定义,不包括任何左值变换;身份转换序列是 被认为是任何非身份转换序列的子序列),或者,如果不是,
  • S1的rank优于S2的rank,或者S1和S2的rank相同且可区分 根据以下段落中的规则,或者,如果不是这样,

对于第一次调用,转换顺序为(Identity, Identity)。对于第二次调用,转换顺序为(Identity,Identity)。所以我们在那里是平等的。这些要点都没有区分这两个电话。所以我们继续前进。

  • S1 和 S2 是引用绑定 (8.5.3),两者均不引用 a 的隐式对象参数 没有引用限定符声明的非静态成员函数,并且 S1 将右值引用绑定到 一个右值,S2 绑定一个左值引用。

无关紧要。

  • S1 和 S2 是引用绑定 (8.5.3),S1 将左值引用绑定到函数左值和 S2 将右值引用绑定到函数左值。

没有。

  • S1 和 S2 的区别仅在于它们的资格转换并产生相似的类型 T1 和 T2 (4.4), 分别,类型 T1 的 cv 限定签名是 cv 限定的真子集 T2 类型的签名。

资格转换是指针的事情,不是。

  • S1和S2是引用绑定(8.5.3),引用所引用的类型相同 顶级 cv 限定符除外的类型,以及由 S2 初始化的引用所引用的类型 比由 S1 初始化的引用所引用的类型更具有 cv 限定。

在这种情况下,第一个重载将其第二个参数作为double&amp;,而第二个重载采用const double&amp;。前者的 cv 资格不如后者,所以我们在此停止 - 更喜欢 foo(U&amp;,T&amp;)

只有在确定哪种转换顺序更好的步骤之后,我们才能进入首选更专业模板的步骤。 [over.match.best] 中的完整规则排序为:

鉴于这些定义,一个可行函数 F1 被定义为比另一个可行函数更好的函数 F2 如果对于所有参数 i,ICSi(F1) 不是比 ICSi(F2) 更差的转换序列,则

  • 对于某些参数 j,ICSj(F1) 是比 ICSj(F2) 更好的转换序列,或者,如果不是这样,

这就是我们刚刚经历的。

  • 上下文是由用户定义的转换初始化 [ ... ]​​i>
  • 上下文是直接引用绑定的转换函数初始化 [...]
  • F1 不是函数模板特化,F2 是函数模板特化,或者,如果不是,
  • F1和F2是函数模板特化,F1的函数模板更加特化 根据 14.5.6.2 中描述的部分排序规则,而不是 F2 的模板。

这就是我们选择foo(U&amp;, T&amp;) 的原因。但是,如果您删除 const,那么两个转换序列在所有步骤中都是相同的 - 因此,在这一点上,更专业的模板 (foo(int&amp;, T&amp;)) 会胜出。

请注意,更专业的是确定最佳候选者的最后机制。这是决胜局的最后一场。

另请注意,模板扣除的数量是无关紧要的。 可能在作为模板的重载和不是模板的重载之间进行选择 - 但在具有 x 模板参数的重载和具有 y > x 模板参数的重载。

【讨论】:

  • 您能否举一个“S1 是 S2 的适当子序列”的例子?那是什么意思? “int&”是“const int&”的正确子序列吗?
【解决方案2】:

你在第二个函数中声明你的第二个参数是const。下面的应用更正示例调用第二个:

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    double g = 2.;
    foo(a, g);

    return 0;
}

另一方面,当您在main() 中明确声明第二个参数确实是const 时,应用程序会按预期调用上述示例中的第二个函数:

#include <iostream>

template <class U, class T>
void foo(U&, T&)
{
    std::cout << "first";
}

template <class T>
void foo(int&, const T&)
{
    std::cout << "second";
}

int main()
{
    int a;
    const double g = 2.;
    foo(a, g);

    return 0;
}

【讨论】:

    【解决方案3】:

    观察:

    • 第一个重载将非常量左值作为第二个参数
    • 第二个重载将 const 左值作为第二个参数

    因为您将 g 作为非 const 左值传递,所以编译器会选择第一个重载。

    【讨论】:

      猜你喜欢
      • 2012-12-03
      • 2010-11-22
      • 1970-01-01
      • 2020-07-18
      • 1970-01-01
      • 1970-01-01
      • 2013-03-16
      • 2021-08-10
      • 1970-01-01
      相关资源
      最近更新 更多