当重载模板化函数时,如果编译器可以选择以下任一选项,编译器应如何选择调用哪个版本的函数:
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)
(如果有的话,它似乎应该做什么)。
因此,我有两个主要问题:
func(Parent)
时调用Child
?我可以在我自己的代码中解决这个问题,这个例子是我想要做的简化版本,但我相信这是同样的问题。
答案 0 :(得分:3)
重载决策的规则如下:
通过以下方式挑选最适合的候选人:
一个。选择具有最佳转换顺序的那个(将其视为“执行从参数类型转换为参数类型所需的最少工作”)
湾在功能模板上选择非功能模板
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>
是一个更好的匹配。
有没有办法强制编译器在传递
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