【问题标题】:C++ templated class with different behaviour depending on typeC++ 模板类具有不同的行为,具体取决于类型
【发布时间】:2017-03-27 20:52:18
【问题描述】:

假设你有这样的课程:

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-statement 能够执行不同的操作。

我会很感激你的回答。 问候,

标签

【问题讨论】:

  • 你不能使用模板专业化吗?
  • 我没有想到这一点,但我担心使用模板规范可能会使该类仅可用于某些类型?我想尽可能保持灵活性
  • @tagelicht,特化比任何类型的 if 都更灵活,因为您可以根据需要添加特化,并且可以添加部分特化以涵盖更复杂的类型集静态ifs。

标签: c++ templates


【解决方案1】:

在 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 上给了 a tutorial talk 这个问题。

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 中,您可能想要使用 函数重载模板特化。前一个示例(更新了来自 cmets 中 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>{});
}

【讨论】:

  • 标签调度 (call(tag&lt;T&gt;{});) 似乎比 call_if_int(..); call_if_string(..) 更好
  • 所有这些方法都是非常糟糕的代码气味。是的,问题正是要求这个,但在绝大多数情况下,特质类型会是更好的方法。那是访客模式与访客模式的静态模拟。用于动态多态对象的 if 链。
  • @JanHudec:我不同意——这取决于编译时分支的范围和可重用性。如果它是一个小而合理的本地分支,那么与 if constexpr 相比,一个 trait 是多余的。
  • @VittorioRomeo,它很小,那么是的,但是 OP 满足“我希望尽可能保持灵活性”,如果 constexpr 绝对不是那样。
【解决方案2】:

尚未举例说明的两种方法是 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");
}

Demo

辅助函数如下所示:

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;
}

这对标签结构应该产生没有开销,因为辅助函数实际上并不使用标签参数,所以它会被优化掉。如果不是,则构造结构是 1 字节的开销。 (注:Vittorio Romeo hinted at this in his answer,但他称之为“函数重载”)


模板专业化

只要你完全特化它(指定类的类型!),你就可以为你的函数“特化”模板:

专业化的语法看起来有点奇怪,因为我们将尖括号留空:&lt;&gt;:

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();
};

Demo 2


结论:

在您给出的具体示例中,我更喜欢模板专业化。然而,通常你想做一些部分特化,这对成员函数来说很奇怪,所以标签分派通常是首选。

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

【讨论】:

  • @tagelicht:不客气。希望有一天,这里的 C++ 社区会被认为比过去少angry
【解决方案3】:

这个怎么样:

#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 才能编译。

【讨论】:

  • 这种方法要求两个分支都有效,即使条件不满足 - 您应该在将其与 if constexpr 进行比较时提及这一点。
  • @VittorioRomeo 我做到了。
  • 感谢您的快速回答:)
  • 我不推荐这种方法,主要是因为所有分支中的块必须针对每种类型进行编译。
  • @AndyG 不过,它根本不会影响性能。如果你想使用模板,在 C++11 中是没有办法的。
猜你喜欢
  • 2021-05-25
  • 1970-01-01
  • 1970-01-01
  • 2019-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多