函数模板参数推导和继承
Function template argument deduction and inheritance
我以为会调用具有最具体的匹配参数类型的函数重载,但我似乎不了解模板和继承类型组合时类型推导的一个方面。
示例:
#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
这对我来说很有意义,因为对象 foo
和 bar
可能是
作为 const Foo&
传递并且由于没有编译时向下转换,
bar
必须被标识为 Foo
.
类型
2。仅定义 GENERIC
$ g++ -DGENERIC main.cpp -o generic && ./generic
输出:
(G) typeid: 3Foo
(G) typeid: 3Bar
这也是有道理的,因为 foo
和 bar
都是左值,可以传递给采用通用常量引用的函数。这将打印每个对象的实际类型。
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 测试
This one confuses me. Having already established that both objects may be passed to both functions, I expected that because const Foo&
is a more specific compatible type for bar
that we would have had the same output as in Case 1. Why does this happen?
但是 const Generic &
,当 Generic
被推断为 Bar
时,对于 Bar
对象比 [=16] 对象更好匹配(是完全匹配) =].
因此在使用 Bar
对象调用时首选和选择 print_typeid()
的模板版本。
相反,使用 const Foo &
对象调用 print_typeid()
,两个版本匹配,完全匹配,非模板版本优先于模板版本。
首先,typeid
仅当参数具有多态 class 类型时才以多态行为。 Foo
和 Bar
都不是多态的,因为它们没有任何虚函数或虚基 classes。所以你的 print_typeid
函数都没有查看对象的实际类型,只是表达式的声明类型。如果您将 virtual ~Foo() = default;
添加到 class Foo
,您会看到不同的行为。
只有FOO
:f
的声明类型是const Foo&
,所以你得到typeinfo
for Foo
例.
只有GENERIC
:在print_typeid(foo);
中推导的模板参数类型是Foo
,所以你得到typeinfo
for Foo
.但是在 print_typeid(bar);
中推导的类型是 Bar
,你得到 Bar
的 typeinfo
。
FOO
和 GENERIC
:的确,在重载解析方面,非模板函数胜过模板函数,更专业的模板函数胜过不太专业的模板函数, 如果函数不明确的话。但是只有当两个调用的隐式转换序列足够接近相同以至于根据参数类型和参数类型都不能被视为更好时,这条规则才会生效。
对于print_typeid(foo)
,编译器首先对函数模板进行类型推导,得到GENERIC=Foo
。所以函数模板的特化是一个带有签名 void print_typeid(const Foo&);
的潜在函数。由于这与非模板函数相同,因此非模板函数获胜。
对于print_typeid(bar)
,编译器再次进行类型推导,这次得到GENERIC=Bar
。函数模板的特化具有签名 void print_typeid(const Bar&);
。所以调用非模板函数需要一个派生到基的转换,而调用模板特化只是一个限定转换(添加const
)。资格转换更好,因此模板胜过重载决议。
我以为会调用具有最具体的匹配参数类型的函数重载,但我似乎不了解模板和继承类型组合时类型推导的一个方面。
示例:
#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
这对我来说很有意义,因为对象 foo
和 bar
可能是
作为 const Foo&
传递并且由于没有编译时向下转换,
bar
必须被标识为 Foo
.
2。仅定义 GENERIC
$ g++ -DGENERIC main.cpp -o generic && ./generic
输出:
(G) typeid: 3Foo
(G) typeid: 3Bar
这也是有道理的,因为 foo
和 bar
都是左值,可以传递给采用通用常量引用的函数。这将打印每个对象的实际类型。
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 测试
This one confuses me. Having already established that both objects may be passed to both functions, I expected that because
const Foo&
is a more specific compatible type forbar
that we would have had the same output as in Case 1. Why does this happen?
但是 const Generic &
,当 Generic
被推断为 Bar
时,对于 Bar
对象比 [=16] 对象更好匹配(是完全匹配) =].
因此在使用 Bar
对象调用时首选和选择 print_typeid()
的模板版本。
相反,使用 const Foo &
对象调用 print_typeid()
,两个版本匹配,完全匹配,非模板版本优先于模板版本。
首先,typeid
仅当参数具有多态 class 类型时才以多态行为。 Foo
和 Bar
都不是多态的,因为它们没有任何虚函数或虚基 classes。所以你的 print_typeid
函数都没有查看对象的实际类型,只是表达式的声明类型。如果您将 virtual ~Foo() = default;
添加到 class Foo
,您会看到不同的行为。
只有
FOO
:f
的声明类型是const Foo&
,所以你得到typeinfo
forFoo
例.只有
GENERIC
:在print_typeid(foo);
中推导的模板参数类型是Foo
,所以你得到typeinfo
forFoo
.但是在print_typeid(bar);
中推导的类型是Bar
,你得到Bar
的typeinfo
。FOO
和GENERIC
:的确,在重载解析方面,非模板函数胜过模板函数,更专业的模板函数胜过不太专业的模板函数, 如果函数不明确的话。但是只有当两个调用的隐式转换序列足够接近相同以至于根据参数类型和参数类型都不能被视为更好时,这条规则才会生效。
对于print_typeid(foo)
,编译器首先对函数模板进行类型推导,得到GENERIC=Foo
。所以函数模板的特化是一个带有签名 void print_typeid(const Foo&);
的潜在函数。由于这与非模板函数相同,因此非模板函数获胜。
对于print_typeid(bar)
,编译器再次进行类型推导,这次得到GENERIC=Bar
。函数模板的特化具有签名 void print_typeid(const Bar&);
。所以调用非模板函数需要一个派生到基的转换,而调用模板特化只是一个限定转换(添加const
)。资格转换更好,因此模板胜过重载决议。