执行特定函数取决于类型
Executing a specific function depend on type
我想使一个可以处理不同类型事物的函数合二为一。
我知道重载是一个很好的解决方案。就像,
class C1 {...};
class C2 {...};
void handle(C1& c1){...}
void handle(C2& c2){...}
但是重载方式中有很多重复的代码,因为这 2 个初始化是相同的。这就是为什么我想把它们包在一起。
我有一些想法来实现我的目的。 例如
class C1 {...};
class C2 {...};
void handle_C1(C1 &c1);
void handle_C2(C2 &c2);
template<typename T>
void handle(T &content){
// Initialization was done here
if (std::is_same(C1, T))
handle_C1(content);
if (std::is_same(C2, T))
handle_C2(content);
}
发现编译错误错误,handle_C2参数不匹配,因为我调用时handle_C2参数类型为 C2
C1 c1;
handle_C1<C1>(c1);
根据SFINAE在C++年的说法,我希望编译会忽略这种替换失败,但事实并非如此。
有人可以给我建议吗?
最好的解决方案是重载吗? 如果这是真的,我怎样才能减少重复的代码。
在我看来,你想多了。只需定义重载而无需初始化代码,并定义一个利用重载解析的handle
函数。
class C1 {...};
class C2 {...};
void handle_impl(C1& c1){...} // Remove initialization from your overloads
void handle_impl(C2& c2){...}
template<typename T>
void handle(T &content)
{
// Initialization is done here
// Let the compiler resolve overloads for you
handle_impl(content);
}
当 handle() 使用 T 实例化时,函数的整个主体被实例化。"如果"和它们的身体都被编译了。 虽然你和我知道 is_same() 是一个编译时常量,你可能希望编译器忽略不可能的情况,但它的使用方式是作为运行时值,编译器必须处理 ifs 和语义检查"好像"它们都可能被调用(即使优化器可以消除一个, 这是在编译器进行的有效性测试之后。 因此,您最终会得到调用 handle_C1 和 handle_C2 的代码,两者都传递相同的类型,并且其中一个肯定无效且无法编译。
如果你可以使用 c++17,一个新功能直接解决了这个问题,称为"constexpr if",如果 constexpr 为真,它会使 if 的主体得到处理(除了有效的语句和语法):
template<typename T>
void handle(T &content){
// Initialization was done here
if constexpr (std::is_same(C1, T))
handle_C1(content);
else if constexpr (std::is_same(C2, T))
handle_C2(content);
}
如果你没有 c++17 编译器(或者即使你有!),你应该考虑回到你的原始设计,简单地从每个函数中分解出初始化,并使其成为一个通用的帮助函数。
我认为在您的情况下,重载就是您所需要的。只需为您编写另一个特殊初始化方法来处理每个类,并在模板化版本(在线)中执行常规操作:
void handleSpecial(C1& c1) {
std::cout << "Handling C1n";
}
void handleSpecial(C2& c2) {
std::cout << "Handling C2n";
}
template <typename T>
void handle(T& content) {
std::cout << "Doing generell stuffn";
handleSpecial(content);
}
SFINAE 仅在选择要调用的函数之前工作,在这种情况下,这不会发生,因为可以使用任何类型的函数调用。然后将生成整个主体,并且您会收到编译器错误,因为没有用于C2
的函数handle_C1
,反之亦然。
完成您的步骤:
如果你有多个类,这些类在许多事情上处理相同,但具有单独的字符,那么这可能需要一个基类。 例如,你可以有这样的东西:
class CBase { ... common features of C1, C2, C3, ...
public:
~CBase() {};
virtual void handle() = 0;
};
class C1 : public CBase { ... something specific to C1
public:
virtual void handle() { do what has to be done for C1 }
};
class C2 : public CBase { ... something specific to C2
public:
virtual void handle() { do what has to be done for C2 }
};
现在,听起来这有点像已经解决了这个问题,因为您可以调用C1.handle()或CBase->handle()。如果需要通过外部函数完成,可以执行以下操作:
void handle(CBase *base_ptr) {
base_ptr->handle();
}
就个人而言,我觉得这比通过非常量引用更好,但我知道这是一个大辩论。
关于您的SFINAE: SFINAE 只是说,如果模板替换失败,编译器将继续寻找匹配项,而不是直接抛出错误。但是如果根本找不到匹配项,它将引发错误。在您的情况下,例如,如果 T = C2,它仍将尝试编译 handle_C1(C2),这会失败。(您的"if case"是运行时决策,而编译器在编译时做出这些决定)
您可以恢复到旧的指针别名。允许将指向一个类型的指针强制转换为指向另一个类型的指针,如果指向的值没有正确的类型,则只需 UB 取消引用指针。
但在这里你知道,在运行时,类型是正确的,因为std::is_same
保证了它。所以我会写:
template<typename T>
void handle(T &content){
// Initialization was done here
if (std::is_same<C1, T>()) {
C1* c = reinterpret_cast<C1*>(&content);
handle_C1(*c);
}
if (std::is_same<C2, T>()) {
C2* c = reinterpret_cast<C2*>(&content);
handle_C2(*c);
}
}
它只是花费自动指针分配和复制,一个真正聪明的优化编译器可以避免,因为在低级机器代码中,它显然是一个无操作。
这是另一种解决方案,它只是将两种行为共有的代码提取到自己的函数中:
class C1 { /* ... */ };
class C2 { /* ... */ };
template<typename T>
void handle_init(T& content) {
// Common initialization code
}
void handle(C1& c1) {
handle_init(c1);
// C1-specific code
}
void handle(C2& c2) {
handle_init(c2);
// C2-specific code
}
根据handle_init
内部的代码,如果函数的主体只调用content
的"getter"方法,则模板可能不是必需的,而这些方法可以事先调用并由handle
函数传递给handle_init
。
我handle
命名这两个函数是为了与您提供的代码保持一致,但您可以为它们指定不同的名称,它仍然适用于大多数用例。重载的大多数用法都是这种情况,因为重载解析使用参数的编译时类型,作为程序员,您通常在调用站点上知道这一点。
一个很大的例外是,如果要handle
的参数是模板参数,在这种情况下,您需要使用重载(因此,其他一些答案实际上确实需要重载)。请注意,要根据参数的运行时类型调用不同的函数,重载不起作用,您需要像 Cedric 的答案那样的虚拟方法。
但除了上面的段落之外,拥有一个处理不同类型的参数的函数与为每个参数类型使用不同的函数在功能上并没有真正的区别。当然,也许在你的问题领域,将其视为前者是有意义的。使用重载handle
调用这两个函数将提供符号上的便利性。我只是认为在执行此操作时有意识地意识到这一点是件好事,以防事实证明重载没有您需要的功能。
- SFINAE是否取决于类型推断?
- 将强制转换简化为取决于参数的类型
- cpp 模板专用化,错误说参数 1 的类型为 T,这取决于参数 T
- 编译时函数的选择取决于类型大小
- memcpy是否取决于源指针和目标指针的类型
- 如果条件取决于模板类型并且在编译时已知,是否可以保证C++编译器不会生成分支?
- 执行特定函数取决于类型
- 模板函数,其中模板参数类型取决于函数参数
- clang 拒绝具有尾随 decltype 返回类型的模板调用是否正确,具体取决于其重载之一?
- 具有尾随返回类型的通用 lambda,具体取决于 C++11 中的可变参数
- 基类数据成员类型取决于派生类
- 类型取决于模板中的条件
- ExprTK 未知变量分辨率取决于表达式类型
- 函数模板中的无效转换错误,返回值取决于其泛型类型
- C++调用具有不同签名的函数,具体取决于类型
- 通用 cout,可以是 wcout,具体取决于类型定义
- 模板函数实现中的条件,具体取决于类型是否为指针
- 不同的值取决于c++类型
- 模板函数中的默认参数值,取决于类型
- 引用全局变量的模板函数(取决于类型)