C ++模板化函数重载规则

时间:2015-07-22 12:46:39

标签: c++ templates overloading overload-resolution

当重载模板化函数时,如果编译器可以选择以下任一选项,编译器应如何选择调用哪个版本的函数:

  • 调用函数的模板化版本(例如func<T>(foo))。
  • 调用函数的重载版本,该函数本身不是模板化的,但传递给函数的参数类型继承自重载函数模板中指定的类型。

考虑以下C ++代码:

#include <stdio.h>

struct Parent {}; 
struct Child : public Parent {}; 

template <typename T>
void func(T) {
  printf("func(T)\n");
}

void func(Parent) {
  printf("func(Parent)\n");
}

int main() {
  func(1);
  func(Parent());
  func(Child());
}

使用gcc或clang编译,输出:

func(T)
func(Parent)
func(T)

预计前两行是有意义的。但是,在通话func(Child())中,它可以轻松地调用func(Parent)(如果有的话,它似乎应该做什么)。

因此,我有两个主要问题:

  • 标准规定了如何解决此类冲突的具体规则是什么?有一些信息in this question/answer,但如果有的话,它与我观察的内容相冲突。
  • 有没有办法强制编译器在传递func(Parent)时调用Child

我可以在我自己的代码中解决这个问题,这个例子是我想要做的简化版本,但我相信这是同样的问题。

2 个答案:

答案 0 :(得分:3)

重载决策的规则如下:

  1. 按名称
  2. 查找所有候选函数
  3. 执行模板扣除并剪裁到可行的候选人(即丢弃不正确的电话)。
  4. 通过以下方式挑选最适合的候选人:

    一个。选择具有最佳转换顺序的那个(将其视为“执行从参数类型转换为参数类型所需的最少工作”)
     湾在功能模板上选择非功能模板
     C。选择最专业的功能模板

  5. 让我们逐个讨论这些问题。对于你的函数调用:

    func(1);
    

    在(2)之后,我们有一个可行的候选人,func<int>func(Parent )不是一个可行的候选者,因为Parent不能从int构建,所以我们已经完成并调用了函数模板。

    func(Parent());
    

    我们有两个可行的候选人:func<Parent>func(Parent )。两者都采用完全相同的参数,因此转换序列是相同的。因此,我们最终在步骤3b:我们在模板上选择非模板,然后调用func(Parent )

    func(Child());
    

    我们有两个可行的候选人:func<Child>func(Parent )。在前一种情况下,参数类型为Child,因此我们传入的内容是完全匹配(无需转换)。在后一种情况下,参数类型为Parent,因此我们必须执行派生到基础的转换。由于功能模板具有更好的转换序列(即不需要转换),因此它被认为是最佳的可行过载。您可以调用func(Parent ) - 这是可行的候选人,但它不是最佳可行的候选人。 func<Child>是一个更好的匹配。

      

    有没有办法强制编译器在传递func(Parent)时调用Child

    您可以自己将Child投射到Parent

    Child c;
    func(static_cast<Parent>(c));
    

    或者您可以编写另一个带有Child的重载,这在第3种情况下是首选(并且仅在第3种情况下可行):

    void func(Child );
    

    或者重写您的函数模板,以便不接受该层次结构中的任何类:

    template <typename T,
              typename = std::enable_if_t<
                  !std::is_convertible<T*, Parent*>::value
              >>
    void func(T );
    

    后一种解决方案(称为SFINAE)将从可行候选者集合中删除func<Child>,以便唯一可行的候选者变为func(Parent )

答案 1 :(得分:1)

为了能够完成任何事情,该标准发明了一份善良清单,以及尝试不同事物的顺序。 STL有一系列关于C9的讲座,详细介绍了这一点。

首先,模板为三种情况中的每一种实例化一个解决方案。 第二个版本选择func(Parent),因为它完全匹配,并赢得模板。第三次调用将模板化版本转换为被认为“不太好”的转换。

我唯一想到的就是做一些可怕的SFINAE来测试每个T,它不能使用类型特征从Parent继承。 C ++ 17中的概念可能允许稍微不那么复杂的东西。

Stephan T. Lavavej: Core C++, 2 of n - Template Argument Deduction

Stephan T. Lavavej: Core C++, 3 of n - Overload Resolution