C++模板化类,根据类型具有不同的行为

C++ templated class with different behaviour depending on type

本文关键字:类型 C++      更新时间:2023-10-16

假设你有一个这样的类:

template <typename T>
class MyClass {
public:
void doSomething();
};

在方法中,有没有办法根据这样的类型具有不同的行为:

template <typename T> MyClass::doSomething() {
//this is what I want:
std::string value = "17";
if(T == int) {
int myInt = atoi(value.c_str());
} else if(T == std::string) {
std::string myString = value;
}
}

有没有简单的方法可以实现这一目标?
我知道我可以编写一个包装类来提供一个接受std::string的构造函数,所以我可以使用类似的东西:

T myVar(value);

但是,如果使用像int这样的简单类型,那会更浪费资源,我也想意识到if-else语句能够执行不同的操作。

我将不胜感激的回答。 问候

塔格利希特

在 C++17 中,您可以使用if constexpr(...)实现您想要的:

template <typename T> MyClass::doSomething() {
//this is what I want:
std::string value = "17";
if constexpr(std::is_same_v<T, int>) {
int myInt = atoi(value.c_str());
} else constexpr(std::is_same_v<T, std::string>) {
std::string myString = value;
}
}

在 C++14 中,您可以非常轻松地实现自己的static_if。我在CppCon 2016和Meeting C++ 2016上对此进行了教程演讲。

template <typename T> MyClass::doSomething() {
//this is what I want:
std::string value = "17";
static_if(std::is_same_v<T, int>)
.then([&](auto) {
int myInt = atoi(value.c_str());
})
.else_if(std::is_same_v<T, std::string>) 
.then([&](auto) {
std::string myString = value;
})();
}

在 C++11 中,您可能希望使用函数重载模板专用化。前者的示例(在评论中使用 Jarod42 的建议进行了更新):

template <typename> struct tag { };
void call_dispatch(...) { } // lowest priority
void call_dispatch(tag<std::string>) 
{ 
std::string myString = value; 
}
void call_dispatch(tag<int>) 
{ 
int myInt = atoi(value.c_str()); 
}

用法:

template <typename T> MyClass::doSomething() {
//this is what I want:
std::string value = "17";
call_dispatch(tag<T>{});
}

尚未举例说明的两种方法是 1)标签调度和 2)模板专用化

标签调度:

  • 调用使用重载的帮助程序函数

我们这样做的方法是将一些空的模板化结构定义为轻量级的"标签"类型:

template <typename T>
class MyClass {
public:
void doSomething();
private:
// tag for dispatch
template<class U>
struct doSomethingTag{};

。然后定义采用该标记专用化的帮助程序函数:

void doSomethingHelper(doSomethingTag<std::string>, std::string value);
void doSomethingHelper(doSomethingTag<int>, std::string value);
};

然后我们可以使用主入口点 (doSomething) 作为调用专用帮助程序函数的一种方式:

template <typename T> 
void MyClass<T>::doSomething() {
//dispatch to helper function
doSomethingHelper(doSomethingTag<T>{}, "17");
}

演示

帮助程序函数如下所示:

template <typename T> 
void MyClass<T>::doSomethingHelper(doSomethingTag<std::string>, std::string value)
{
int myInt = atoi(value.c_str());
}
template <typename T> 
void MyClass<T>::doSomethingHelper(doSomethingTag<int>, std::string value)
{
std::string myString = value;
}

这应该不会给标记结构带来开销,因为帮助程序函数实际上并不使用 tag 参数,因此它将被优化掉。如果不是,则构造结构的开销为 1 字节。(注:维托里奥·罗密欧在回答中暗示了这一点,但他称之为"函数重载")


模板专用化

你可以"专用化"函数的模板,只要你完全专用于它(指定类的类型!

专用化的语法看起来有点奇怪,因为我们保留尖括号为空:<>

template<> 
void MyClass<int>::doSomething() {
//dispatch to helper function
std::string value = "17";
int myInt = atoi(value.c_str());
}
template <>
void MyClass<std::string>::doSomething()
{
std::string value = "17";
std::string myString = value;
}

这使您能够保留较小的类:

template <typename T>
class MyClass {
public:
void doSomething();
};

演示 2


结论:

在您给出的具体示例中,我更喜欢模板专业化。但是,通常您希望执行某种部分专用化,这对于成员函数来说非常非常奇怪,因此标签调度通常是首选。

对于C++17,使用constexpr if肯定会让生活更轻松。

这个怎么样:

#include <type_traits>
template <typename T> MyClass::doSomething() {
//this is what I want:
std::string value = "17";
if(std::is_same<T,int>::value) {
int myInt = atoi(value.c_str());
} else if(std::is_same<T,std::string>::value) {
std::string myString = value;
}
}

这是C++11。你可以用 C++17 和constexpr if让它变得更好。这里有一个问题,即您必须强制转换不可隐式转换的值。例如,必须将std::string的返回转换为 int 才能编译。