用抽象覆盖模板函数 class
Override template function with abstract class
我创建了一个模板函数,定义如下。
template<class T>
void func(T t){ /* do stuff */ }
如果 T 继承自我创建的抽象 class,我想重载此模板。
class A {
public:
virtual void doStuff() = 0;
};
class B : public A {
virtual void doStuff(){ /* do stuff */ }
}
我已经尝试使用模板专业化(如下),但它仍然使用原始定义。
template<>
void func(A& a){ /* do different stuff */ } // Not called by func(B())
我也尝试过重载它,虽然它适用于整数,但不适用于我的基数 class。
func(int i){ /* do stuff with i */ } // Called by func(3)
func(A& a){ /* do different stuff */ } // Not called by func(B())
我猜这与 C++ 不想将我的 B 实例隐式转换为 A 并引用它有关,但我无法找到任何解释我如何修复此行为的内容。由于 A 有一个纯虚函数,我不能只定义 func(A a)
。任何帮助将不胜感激。
这是一个可以重现我遇到的行为的示例。
#include <iostream>
template<class T>
void func(T t){
std::cout << "Template function called!" << std::endl;
}
class A {
public:
virtual void doStuff() = 0;
};
class B : public A{
public:
virtual void doStuff(){};
};
template<>
void func(const A& a){
std::cout << "Specialized template called!" << std::endl;
}
void func(const A& a){
std::cout << "Overload called!" << std::endl;
}
int main(){
B b{};
func(b);
return 0;
}
B b;
func((A&)b);
我认为你不能在这里临时通过。您不能将 B() 转换为 'A&',因为它是一个右值并将其转换为 const A& 使模板版本更匹配。
使用 c++11 的另一个选项(在其他答案之上)- 如果 T 是 B 的子类型,则显式删除模板版本:
template<class T>
typename std::enable_if<!std::is_base_of<A, T>::value>::type
func(T& t){
std::cout << "Template function called!" << std::endl;
}
void func(const A& a){
std::cout << "Overload called!" << std::endl;
}
int main(){
func(B());
}
C++20 解决方案
使用 C++20 中的概念,我们可以编写一个 inherits_from
概念,然后使用它。概念允许我们约束模板,使其仅适用于表达式为真的情况。
这个概念是这样的:
#include <type_traits>
template<class Derived, class Base>
concept derived_from = std::is_base_of_v<Base, Derived>;
然后,我们就可以编写泛型模板和约束模板了:
struct MyBase{};
struct MyDerived : MyBase{};
// This is the generic template; using auto here is valid in C++20
void do_thing(auto const& thing) {
std::cout << "Doing thing on regular type\n";
}
//This is the template that acts on classes derived from MyBase
void do_thing(derived_from<MyBase> const& x) {
std::cout << "Doing thing on MyBase\n";
}
因为第二个函数将 T
声明为遵循概念 inherits_from
,它更专业,所以对于实际继承自 MyBase
的类型,将选择泛型模板:
int main() {
do_thing(10); // Prints "Doing thing on regular type"
do_thing(MyBase()); // Prints "Doing thing on MyBase"
do_thing(MyDerived()); // Prints "Doing thing on MyBase"
}
C++17 解决方案
我们可以使用 SFINAE 模拟概念的行为,尽管这需要修改通用模板,以便在 T
扩展 MyBase
时忽略它。使用 SFINAE 的关键是在条件为假时触发替换失败,从而导致该重载被忽略。
为了触发替换失败,在模板参数列表的末尾添加默认模板参数。在我们的例子中,它看起来像这样:
template<
class T,
// This defaulted argument triggers the substitution failure
class = std::enable_if_v</* condition */>>
在我们的代码中,
- 如果 T
扩展 MyBase
,通用重载将被禁用
- 如果 T
不 扩展 MyBase
,约束重载将被禁用
看起来像这样:
struct MyBase {};
struct MyDerived : MyBase {};
template<
class T,
class = std::enable_if_t<!std::is_base_of_v<MyBase, T>>>
void do_thing(T const&) {
std::cout << "Doing thing on regular type\n";
}
// This overload is *disabled* if T doesn't inherit from MyBase
template<
class T,
// We have to have an additional defaulted template argument to distinguish between the overloads
class = void,
class = std::enable_if_t<std::is_base_of_v<MyBase, T>>>
void do_thing(T const& x) {
std::cout << "Doing thing on MyBase\n";
}
尽管声明很奇怪,我们仍然可以像使用常规函数一样使用 do_thing
:
int main() {
do_thing(10);
do_thing(MyBase());
do_thing(MyDerived());
}
向后移植 C++11
我们只需要做一些小的改动就可以向后移植到 C++11。基本上,
is_base_of_v<MyBase, T>
要换成is_base_of<MyBase, T>::value
,而
enable_if_t</* condition */>
必须替换为 typename enable_if</* condition */>::type
如果您可以访问 C++17,那么您可以创建一个 public 重载,它将转发到正确的函数:
namespace detail {
template<class T>
void func(T t) {
std::cout << "Template function called!" << std::endl;
}
void func(A& a){
std::cout << "Overload called!" << std::endl;
}
}
template<class T>
void func(T& t) {
if constexpr (std::is_base_of_v<A, T>) {
detail::func(static_cast<A&>(t));
} else {
detail::func(t);
}
}
int main() {
B b;
func(b);
}
否则,您可以使用标签调度或 SFINAE:
标签调度:
template<class T>
void func(T t, std::false_type) {
std::cout << "Template function called!" << std::endl;
}
void func(A& a, std::true_type) {
std::cout << "Other function called!" << std::endl;
}
template<class T>
void func(T& t) {
func(t, std::is_base_of<A, T>{});
}
SFINAE:
template<class T,
std::enable_if_t<std::is_base_of_v<A, T>>* = nullptr>
void func(T t) {
std::cout << "Template function called!" << std::endl;
}
template<class T,
std::enable_if_t<!std::is_base_of_v<A, T>>* = nullptr>
void func(T& t) {
std::cout << "Other function called!" << std::endl;
}
我创建了一个模板函数,定义如下。
template<class T>
void func(T t){ /* do stuff */ }
如果 T 继承自我创建的抽象 class,我想重载此模板。
class A {
public:
virtual void doStuff() = 0;
};
class B : public A {
virtual void doStuff(){ /* do stuff */ }
}
我已经尝试使用模板专业化(如下),但它仍然使用原始定义。
template<>
void func(A& a){ /* do different stuff */ } // Not called by func(B())
我也尝试过重载它,虽然它适用于整数,但不适用于我的基数 class。
func(int i){ /* do stuff with i */ } // Called by func(3)
func(A& a){ /* do different stuff */ } // Not called by func(B())
我猜这与 C++ 不想将我的 B 实例隐式转换为 A 并引用它有关,但我无法找到任何解释我如何修复此行为的内容。由于 A 有一个纯虚函数,我不能只定义 func(A a)
。任何帮助将不胜感激。
这是一个可以重现我遇到的行为的示例。
#include <iostream>
template<class T>
void func(T t){
std::cout << "Template function called!" << std::endl;
}
class A {
public:
virtual void doStuff() = 0;
};
class B : public A{
public:
virtual void doStuff(){};
};
template<>
void func(const A& a){
std::cout << "Specialized template called!" << std::endl;
}
void func(const A& a){
std::cout << "Overload called!" << std::endl;
}
int main(){
B b{};
func(b);
return 0;
}
B b;
func((A&)b);
我认为你不能在这里临时通过。您不能将 B() 转换为 'A&',因为它是一个右值并将其转换为 const A& 使模板版本更匹配。
使用 c++11 的另一个选项(在其他答案之上)- 如果 T 是 B 的子类型,则显式删除模板版本:
template<class T>
typename std::enable_if<!std::is_base_of<A, T>::value>::type
func(T& t){
std::cout << "Template function called!" << std::endl;
}
void func(const A& a){
std::cout << "Overload called!" << std::endl;
}
int main(){
func(B());
}
C++20 解决方案
使用 C++20 中的概念,我们可以编写一个 inherits_from
概念,然后使用它。概念允许我们约束模板,使其仅适用于表达式为真的情况。
这个概念是这样的:
#include <type_traits>
template<class Derived, class Base>
concept derived_from = std::is_base_of_v<Base, Derived>;
然后,我们就可以编写泛型模板和约束模板了:
struct MyBase{};
struct MyDerived : MyBase{};
// This is the generic template; using auto here is valid in C++20
void do_thing(auto const& thing) {
std::cout << "Doing thing on regular type\n";
}
//This is the template that acts on classes derived from MyBase
void do_thing(derived_from<MyBase> const& x) {
std::cout << "Doing thing on MyBase\n";
}
因为第二个函数将 T
声明为遵循概念 inherits_from
,它更专业,所以对于实际继承自 MyBase
的类型,将选择泛型模板:
int main() {
do_thing(10); // Prints "Doing thing on regular type"
do_thing(MyBase()); // Prints "Doing thing on MyBase"
do_thing(MyDerived()); // Prints "Doing thing on MyBase"
}
C++17 解决方案
我们可以使用 SFINAE 模拟概念的行为,尽管这需要修改通用模板,以便在 T
扩展 MyBase
时忽略它。使用 SFINAE 的关键是在条件为假时触发替换失败,从而导致该重载被忽略。
为了触发替换失败,在模板参数列表的末尾添加默认模板参数。在我们的例子中,它看起来像这样:
template<
class T,
// This defaulted argument triggers the substitution failure
class = std::enable_if_v</* condition */>>
在我们的代码中,
- 如果 T
扩展 MyBase
,通用重载将被禁用
- 如果 T
不 扩展 MyBase
看起来像这样:
struct MyBase {};
struct MyDerived : MyBase {};
template<
class T,
class = std::enable_if_t<!std::is_base_of_v<MyBase, T>>>
void do_thing(T const&) {
std::cout << "Doing thing on regular type\n";
}
// This overload is *disabled* if T doesn't inherit from MyBase
template<
class T,
// We have to have an additional defaulted template argument to distinguish between the overloads
class = void,
class = std::enable_if_t<std::is_base_of_v<MyBase, T>>>
void do_thing(T const& x) {
std::cout << "Doing thing on MyBase\n";
}
尽管声明很奇怪,我们仍然可以像使用常规函数一样使用 do_thing
:
int main() {
do_thing(10);
do_thing(MyBase());
do_thing(MyDerived());
}
向后移植 C++11
我们只需要做一些小的改动就可以向后移植到 C++11。基本上,
is_base_of_v<MyBase, T>
要换成is_base_of<MyBase, T>::value
,而enable_if_t</* condition */>
必须替换为typename enable_if</* condition */>::type
如果您可以访问 C++17,那么您可以创建一个 public 重载,它将转发到正确的函数:
namespace detail {
template<class T>
void func(T t) {
std::cout << "Template function called!" << std::endl;
}
void func(A& a){
std::cout << "Overload called!" << std::endl;
}
}
template<class T>
void func(T& t) {
if constexpr (std::is_base_of_v<A, T>) {
detail::func(static_cast<A&>(t));
} else {
detail::func(t);
}
}
int main() {
B b;
func(b);
}
否则,您可以使用标签调度或 SFINAE:
标签调度:
template<class T>
void func(T t, std::false_type) {
std::cout << "Template function called!" << std::endl;
}
void func(A& a, std::true_type) {
std::cout << "Other function called!" << std::endl;
}
template<class T>
void func(T& t) {
func(t, std::is_base_of<A, T>{});
}
SFINAE:
template<class T,
std::enable_if_t<std::is_base_of_v<A, T>>* = nullptr>
void func(T t) {
std::cout << "Template function called!" << std::endl;
}
template<class T,
std::enable_if_t<!std::is_base_of_v<A, T>>* = nullptr>
void func(T& t) {
std::cout << "Other function called!" << std::endl;
}