函数模板参数推导和继承

时间:2019-03-02 19:50:57

标签: c++ templates inheritance template-deduction type-deduction

我以为会调用具有最特定匹配参数类型的函数重载,但是当模板和继承的类型组合在一起时,我似乎不理解类型推导的一个方面。

示例:

#include<iostream>
#include<typeinfo>

struct Foo {};
struct Bar : Foo {};

#ifdef FOO
void print_typeid( const Foo& f ) {
  std::cout << "(F) typeid: " << typeid(f).name() << std::endl;
}
#endif // FOO

#ifdef GENERIC  
template<typename Generic>
void print_typeid( const Generic& g ) {
  std::cout << "(G) typeid: " << typeid(g).name() << std::endl;
}  
#endif // GENERIC

int main( int argc, char *argv[] ) {

  Foo foo; print_typeid(foo); 
  Bar bar; print_typeid(bar);

  return 0;
}

测试用例

1。仅定义FOO

$ g++ -DFOO main.cpp -o foo && ./foo

输出:

(F) typeid: 3Foo
(F) typeid: 3Foo

这对我来说很有意义,因为对象foobar可能是 作为const Foo&传递,并且由于没有编译时下调, 必须将bar标识为类型Foo

2。仅定义GENERIC

$ g++ -DGENERIC main.cpp -o generic && ./generic

输出:

(G) typeid: 3Foo
(G) typeid: 3Bar

这也很有意义,因为foobar都是左值,可以传递给采用通用常量引用的函数。这将打印每个对象的实际类型。

3。定义FOO和GENERIC

$ g++ -DFOO -DGENERIC main.cpp -o both && ./both

输出:

(F) typeid: 3Foo
(G) typeid: 3Bar

这使我感到困惑。已经确定可以将两个对象都传递给两个函数,所以我期望,因为const Foo&bar的更特定的兼容类型,因此我们将获得与案例1相同的输出。为什么这样做发生了吗?

使用gcc 7.2和clang 4测试

2 个答案:

答案 0 :(得分:2)

  

这使我感到困惑。已经确定可以将两个对象都传递给两个函数,所以我期望,因为const Foo&bar的更特定的兼容类型,因此我们将获得与案例1相同的输出。为什么这样做发生了吗?

但是,当const Generic &推论为Generic时,Bar对象的匹配(精确匹配)比Bar好。

因此,首选const Foo &的模板版本,并选择通过print_typeid()对象调用的模板。

相反,用Bar对象调用print_typeid()时,两个版本都完全匹配,并且非模板版本优于模板版本。

答案 1 :(得分:1)

首先,typeid仅在参数具有多态类类型时才具有多态作用。 FooBar都不是多态的,因为它们没有任何虚函数或虚基类。因此,您的print_typeid函数都不在查看对象的实际类型,而只是在声明表达式的类型。如果您将virtual ~Foo() = default;添加到类Foo,则会看到不同的行为。

  1. FOOf的声明类型为const Foo&,因此在两种情况下都为typeinfo获得了Foo

  2. GENERIC:在print_typeid(foo);中推导的模板参数类型为Foo,因此您得到typeinfo的{​​{1}}。但是在Foo中推导的类型为print_typeid(bar);,您将得到Bar的{​​{1}}。

  3. typeinfoBar两者都是:在重载解析中,非模板函数确实胜过模板函数,而更专业的模板函数胜过了较不专业的模板函数。否则将是模棱两可的。但是,只有当两个调用的隐式转换序列足够接近相同的规则,并且根据参数类型和参数类型都无法将其视为更好时,该规则才会生效。

对于FOO,编译器首先对函数模板进行类型推导,得到GENERIC。因此,功能模板的专业化是具有签名print_typeid(foo)的潜在功能。由于这与非模板功能相同,因此非模板功能将获胜。

对于GENERIC=Foo,编译器再次进行类型推导,这次得到void print_typeid(const Foo&);。功能模板的特殊化具有签名print_typeid(bar)。因此,调用非模板函数需要从派生到基本的转换,而调用模板特化仅是限定转换(添加GENERIC=Bar)。资格转换效果更好,因此模板可以赢得重载分辨率。