如何基于模板参数类型转换类模板

时间:2019-06-28 14:48:39

标签: c++ c++11 templates casting

当前,我将不同类型的指针存储在向量中。为了对此进行归档,我实现了一个类模板“ Store”,该类模板派生自非类模板“ IStore”。我的向量最终存储了指向“ IStore”的指针。 在代码中:

class IStore
{
public:
    IStore() = default;
    virtual ~IStore() = default;

    virtual void call() = 0;
    // ... other virtual methods
};

template<typename T>
class Store : public IStore
{
public:
    Store() = default;
    virtual ~Store() = default;

    virtual void call() override;
    // ... other virtual methods

private:
    T* m_object = nullptr;
}

在我的主类中有向量:

class Main
{
public:
    template<typename T>
    void registerObject(T* ptr);

    template<typename T>
    void callObjects();
    // ... other methods

private:
    std::vector<IStore*> m_storedObjects;
};

到目前为止,当前的类结构。为了描述这个问题,我需要介绍以下三个示例结构:

struct A {}
struct B : public A {}
struct C : {}

其他类应使用指向A,B或C类型对象的指针来调用Main :: registerObject方法。然后,此方法将创建一个新的Store 和Store 。 Store 模板类对象,并将此对象的指针插入到m_storedObjects。

现在,棘手的部分开始了:Main :: callObjects方法应该由其他带有模板参数的类调用,例如Main :: callObjects ()。这应该遍历m_storedObjects并为每个对象(类型为B或类型B派生)调用IStore :: call方法。

例如:

Main::registerObject<A>(obj1);
Main::registerObject<B>(obj2);
Main::registerObject<C>(obj3);
Main::callObjects<B>();

应该调用obj1和obj2而不是obj3,因为C不是B,并且B不是从C派生的。

我在Main :: callObjects中的方法是: 1.执行dynamic_cast并对照nullptr进行检查,例如:

for(auto store : m_storedObjects)
{
    Store<T>* base = dynamic_cast<Store<T>*>(store);
    if(base)
    {
        // ...
    }
}

仅适用于相同的类,不适用于派生类,因为Store 不是派生自Store 。 2.要覆盖IStore和Store中的强制转换运算符,以便当template参数可转换时,我可以指定Store应该可转换。例如在商店中:

template<typename C>
operator Store<C>*()
{
    if(std::is_convertible<T, C>::value)
    {
        return this;
    }
    else
    {
        return nullptr;
    }
}

但是永远不会调用此方法。

有人可以解决这个问题吗? 抱歉,很长的帖子,但我认为更多的代码会更好地理解问题。 无论如何,谢谢您的帮助:)

1 个答案:

答案 0 :(得分:0)

经过一番思考,我意识到从将Store<T>对象分配给IStore*指针到您的类型擦除,使得不可能使用任何编译时类型检查,例如std::is_base_of等。 。下一个最佳选择是运行时类型信息(dynamic_cast<>()typeid())。如您所见,dynamic_cast<>()不能确定对象的类型是否是另一种类型的祖先,仅当对象的类型是在编译时已知的另一种类型的后代。

编辑:借助C ++ 17支持,我可以根据std::visit示例here考虑​​另一种解决您的问题的方法。如果您更改Main界面...

#include <iostream>
#include <vector>
#include <variant>

template <typename T>
class Store {
public:
    using value_type = T;

    Store(T* object): m_object(object) {}

    void call() { std::cout << "Hello from " << typeid(T).name() << '\n'; }
    // ... other methods

private:
    T* m_object = nullptr;
};



template <typename... Ts>
class Main {
private:
    std::vector<std::variant<Store<Ts>...>> m_storedObjects;

public:
    // replacement for registerObjects, if you can take all objects in at once
    Main(Ts*... args): m_storedObjects({std::variant<Store<Ts>...>(Store<Ts>{args})...}) {}

    template <typename U>
    void callObjects() {
        for (auto& variant : m_storedObjects) {
            std::visit([](auto&& arg) {
                using T = typename std::decay_t<decltype(arg)>::value_type;
                if constexpr (std::is_base_of<T, U>::value) {
                    arg.call();
                }
            }, variant);
        } 
    }

};


struct A {};
struct B : public A {};
struct C {};

int main() {
    A a;
    B b;
    C c;
    auto m = Main{&a, &b, &c};
    m.callObjects<B>();
    // > Hello from 1A
    // > Hello from 1B
    return 0; 
}