为何此代码中foo(d)调用模板函数而非Base函数?有无无需新增重载的调用方法?
为什么调用的是模板函数?
嘿,这个问题特别典型,正好能帮你理清C++重载决议的核心规则~
咱们先拆解代码里的重载候选:当你调用foo(d)时,编译器有两个可选的函数:
- 模板实例化出来的
foo<Derived>(Derived val)——这个是精确匹配,因为实参d是Derived类型,模板参数T直接推导成Derived,参数类型完全对应,不需要任何转换。 - 非模板函数
foo(const Base& val)——这个需要把Derived类型的d转换成const Base&,属于派生类到基类的标准转换。
在C++的重载决议规则里,精确匹配的优先级高于需要转换的匹配,哪怕非模板函数看起来更“贴合”你的需求,编译器还是会优先选那个不需要任何转换的模板实例。这就是为什么你看到的是template called而不是Base called。
不用新增重载,怎么让它调用Base版本?
有几种优雅的方法,不用额外写foo(Derived)的重载:
方法1:显式转换(最简单直接)
调用的时候把d转换成const Base&,直接告诉编译器你要匹配哪个版本:
foo(static_cast<const Base&>(d));
这种方法不需要修改原函数,只是调用时加个转换,适合临时调试或者场景简单的情况。
方法2:用SFINAE禁用模板的匹配(更通用)
如果想让编译器自动优先选Base版本,不用每次调用都转换,可以修改模板函数,用SFINAE(替换失败不是错误)让它只处理**不能转换成const Base&**的类型。这样当参数是Derived(或其他Base的派生类)时,模板会被排除在候选列表之外,编译器就会自动选Base的版本:
#include <type_traits> // 只有当T不能转换成const Base&时,这个模板才会被实例化 template <class T, std::enable_if_t<!std::is_convertible_v<T, const Base&>, int> = 0> void foo(T val) { printf("template called\n"); } class Base { }; void foo(const Base &val) { printf("Base called\n"); } class Derived : public Base { }; int main() { Derived d; foo(d); // 现在会调用Base版本! return 0; }
原理是std::is_convertible_v<T, const Base&>会检查T能不能转换成const Base&,如果能(比如Derived),!取反后就是false,std::enable_if_t会导致模板参数替换失败,这个模板就不会被当作候选函数,剩下的只有Base版本的foo,自然就调用它了。
内容的提问来源于stack exchange,提问作者Roddy
相关产品推荐
相关产品推荐

