C++模板函数重载规则

C++ templated function overloading rules

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

考虑以下 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)(这似乎是 应该 做的事情)。

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

我可以在我自己的代码中绕过这个要求,这个例子是我正在尝试做的事情的简化版本,但我相信这是同一个问题。

为了能够完成任何事情,该标准发明了一个优点列表,以及尝试不同事情的顺序。来自 STL 的 C9 系列讲座对此进行了详细介绍。

首先,模板为三种情况中的每一种实例化一个解决方案。 第二个版本选择 func(Parent) 因为它完全匹配,并且胜过模板。第三次调用采用模板化版本进行转换,该转换被视为 "less good".

我唯一的想法就是做一些可怕的 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

重载决议的规则是这样的:

  1. 按名称查找所有候选函数
  2. 执行模板推导并修剪为可行的候选者(即丢弃格式错误的调用)。
  3. 通过以下方式选择最佳可行候选人:

    一个。选择具有最佳转换顺序的那个(将其视为 "doing the least necessary work to convert from the argument types to the parameter types")
    b.在函数模板上选择非函数模板
    C。选择最专业的函数模板

让我们逐案探讨这些问题。对于您的函数调用:

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> 是一个更好的匹配。

Is there any way to force the compiler to call func(Parent) when passed a Child?

您可以自己将 Child 转换为 Parent

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

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

void func(Child );

或者重写您的函数模板,以便在该层次结构中不使用任何 class:

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

后一种解决方案(称为 SFINAE)将从可行候选集中删除 func<Child>,这样唯一可行的候选就变成了 func(Parent )