【问题标题】:Function template argument deduction and inheritance函数模板参数推导和继承
【发布时间】:2019-03-02 19:50:57
【问题描述】:

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

例子:

#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 &amp;&amp; ./foo

输出:

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

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

2。仅定义 GENERIC

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

输出:

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

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

3.定义 FOO 和 GENERIC

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

输出:

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

这个让我很困惑。已经确定两个对象都可以传递给这两个函数,我预计因为const Foo&amp;bar 的更具体的兼容类型,所以我们会得到与案例 1 相同的输出。为什么会发生这种情况?

使用 gcc 7.2 和 clang 4 测试

【问题讨论】:

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


    【解决方案1】:

    这个让我很困惑。已经确定两个对象都可以传递给这两个函数,我预计因为const Foo&amp;bar 的更具体的兼容类型,所以我们会得到与案例 1 相同的输出。为什么会发生这种情况?

    但是const Generic &amp;,当Generic 被推断为Bar 时,对于Bar 对象比const Foo &amp; 更好匹配(是精确匹配)。

    因此,print_typeid() 的模板版本是首选的,并且在使用 Bar 对象调用时会被选中。

    相反,使用const Foo &amp; 对象调用print_typeid(),两个版本都匹配,完全匹配,并且非模板版本优先于模板版本。

    【讨论】:

      【解决方案2】:

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

      1. FOOf 的声明类型为const Foo&amp;,因此在这两种情况下,Footypeinfo

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

      3. FOOGENERIC:确实,非模板函数胜过模板函数,而更专业的模板函数在重载决议中胜过不专业的模板函数,否则函数会不明确.但是这个规则只有在两个调用的隐式转换序列足够接近相同以至于根据参数类型和参数类型都不能被视为更好时才会生效。

      对于print_typeid(foo),编译器首先对函数模板进行类型推导,得到GENERIC=Foo。所以函数模板的特化是一个带有签名void print_typeid(const Foo&amp;);的潜在函数。由于这与非模板函数相同,因此非模板函数获胜。

      对于print_typeid(bar),编译器再次进行类型推导,这次得到GENERIC=Bar。函数模板的特化有签名void print_typeid(const Bar&amp;);。所以调用非模板函数需要一个派生到基的转换,而调用模板特化只是一个限定转换(添加const)。限定转换更好,因此模板赢得重载决议。

      【讨论】:

        猜你喜欢
        • 2023-03-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-12-05
        • 2021-08-31
        • 1970-01-01
        • 2018-07-17
        • 1970-01-01
        相关资源
        最近更新 更多