C++模板函数重载规则
C++ templated function overloading rules
重载模板化函数时,如果编译器可以选择调用哪个版本的函数,编译器应如何选择:
- 调用函数的模板版本(例如
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)
?
我可以在我自己的代码中绕过这个要求,这个例子是我正在尝试做的事情的简化版本,但我相信这是同一个问题。
为了能够完成任何事情,该标准发明了一个优点列表,以及尝试不同事情的顺序。来自 STL 的 C9 系列讲座对此进行了详细介绍。
首先,模板为三种情况中的每一种实例化一个解决方案。
第二个版本选择 func(Parent)
因为它完全匹配,并且胜过模板。第三次调用采用模板化版本进行转换,该转换被视为 "less good".
我唯一的想法就是做一些可怕的 SFINAE,测试每个 T 它不能使用类型特征从 Parent
继承。 C++17 中的概念可能允许一些稍微不那么复杂的事情。
见
Stephan T. Lavavej: Core C++, 2 of n - Template Argument Deduction
重载决议的规则是这样的:
- 按名称查找所有候选函数
- 执行模板推导并修剪为可行的候选者(即丢弃格式错误的调用)。
通过以下方式选择最佳可行候选人:
一个。选择具有最佳转换顺序的那个(将其视为 "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 )
。
重载模板化函数时,如果编译器可以选择调用哪个版本的函数,编译器应如何选择:
- 调用函数的模板版本(例如
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)
?
我可以在我自己的代码中绕过这个要求,这个例子是我正在尝试做的事情的简化版本,但我相信这是同一个问题。
为了能够完成任何事情,该标准发明了一个优点列表,以及尝试不同事情的顺序。来自 STL 的 C9 系列讲座对此进行了详细介绍。
首先,模板为三种情况中的每一种实例化一个解决方案。
第二个版本选择 func(Parent)
因为它完全匹配,并且胜过模板。第三次调用采用模板化版本进行转换,该转换被视为 "less good".
我唯一的想法就是做一些可怕的 SFINAE,测试每个 T 它不能使用类型特征从 Parent
继承。 C++17 中的概念可能允许一些稍微不那么复杂的事情。
见
Stephan T. Lavavej: Core C++, 2 of n - Template Argument Deduction
重载决议的规则是这样的:
- 按名称查找所有候选函数
- 执行模板推导并修剪为可行的候选者(即丢弃格式错误的调用)。
通过以下方式选择最佳可行候选人:
一个。选择具有最佳转换顺序的那个(将其视为 "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 aChild
?
您可以自己将 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 )
。