【问题标题】:C++ templated function overloading rulesC++模板函数重载规则
【发布时间】:2015-07-22 12:46:38
【问题描述】:

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

  • 调用函数的模板版本(例如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,但如果有什么与我观察到的冲突。
  • 有什么方法可以强制编译器在传递Child 时调用func(Parent)

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

【问题讨论】:

标签: c++ templates overloading overload-resolution


【解决方案1】:

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

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

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

让我们逐案讨论。对于您的函数调用:

func(1);

在 (2) 之后,我们有一个可行的候选人,func&lt;int&gt;func(Parent ) 不是一个可行的候选者,因为 Parent 不能从 int 构造,所以我们完成并调用函数模板。

func(Parent());

我们有两个可行的候选人:func&lt;Parent&gt;func(Parent )。两者都采用完全相同的参数,因此转换序列相同。所以我们在步骤 3b 结束:我们选择非模板而不是模板,我们调用func(Parent )

func(Child());

我们有两个可行的候选人:func&lt;Child&gt;func(Parent )。在前一种情况下,参数类型是Child,所以它是我们传入的精确匹配(无需转换)。在后一种情况下,参数类型是Parent,因此我们必须执行派生到基的转换。由于函数模板具有更好的转换顺序(即不需要转换),因此它被认为是最佳可行的重载。您可以致电func(Parent ) - 这是一个可行的候选人,但它不是最佳可行的候选人。 func&lt;Child&gt; 是更好的匹配。

有什么方法可以强制编译器在传递Child 时调用func(Parent)

您可以自己将Child 转换为Parent

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

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

void func(Child );

或者重写你的函数模板,以便不采用该层次结构中的任何类:

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

后一种解决方案(称为 SFINAE)将从可行候选者集中删除 func&lt;Child&gt;,因此唯一可行的候选者变为 func(Parent )

【讨论】:

  • func (Parent) 不是 (3) 的可行候选者,否则它会优先于模板函数。
  • @AlexandreC。这绝对是一个可行的候选人。 Child 可转换为 Parent。这只是一场更糟糕的比赛。
  • 我的错。我一直认为非模板总是比模板更受欢迎,但事实证明并非总是如此。这取决于(相当微妙地)什么被认为是“更好”的转换序列。
【解决方案2】:

为了能够完成任何事情,该标准发明了一个优点列表,以及尝试不同事物的顺序。 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

【讨论】:

  • 好的;这基本上是我所期待的。你碰巧有这些讲座的链接或任何更详细的链接吗?
  • “重载胜过模板。”它们都是重载,所以这不是你的意思......
  • “第三次调用采用模板版本而不是转换,这更像是最后的手段。”转换顺序是第一个手段,而不是最后手段。
猜你喜欢
  • 2021-10-28
  • 1970-01-01
  • 1970-01-01
  • 2010-09-18
  • 1970-01-01
  • 2012-09-02
  • 1970-01-01
  • 2021-12-12
  • 2014-04-17
相关资源
最近更新 更多