WARhead
WARhead

Reputation: 691

Changing a Template argument c++

I have created a dynamically expanding container class using template. But this can only store objects of same datatype. How can i create a container class that expands dynamically and can hold objects of types specified when expanding the class.

That is for eg:-

if obj is an object of said class I should be able to call a function say

obj.foo<int>(25);

and after that the same function to store second member as a Boolean type or float

obj.foo<bool>(true);

is there any way to do this I have thought about templates but I cannot set a class member without knowing the datatype before hand

Upvotes: 1

Views: 711

Answers (2)

DiscoStu
DiscoStu

Reputation: 569

here's a c++03 way:

#include <vector>
#include <string>
#include <typeinfo>

class Entry{
public:
    class Exception_TypeFail : std::exception {
    public:
        ~Exception_TypeFail() throw() {
        }

        const char* what() const throw(){
            return "Failed to cast";
        }
    };

    class PolymorphInterface{
    public:
        virtual ~PolymorphInterface() {};
        virtual bool isType(const std::type_info &testType) const =0;
    };

    template<typename TYPE>
    class Polymorph : public PolymorphInterface{
    private:
        TYPE mData;

    public:
        Polymorph(const TYPE &copyMe)
        : mData(copyMe) {
        }

        bool isType(const std::type_info &testType) const {
            return testType == typeid(mData);
        }

        TYPE& get(){
            return mData;
        }
    };

    Entry(PolymorphInterface *manageMe)
    : mManageMe(manageMe) {
    }

    Entry(const Entry &copyMe)
    : mManageMe(const_cast<Entry&>(copyMe).mManageMe) {
        const_cast<Entry&>(copyMe).mManageMe = NULL;
    }

    ~Entry(){
        delete mManageMe;
    }

    template<typename TYPE>
    bool isType(){
        return mManageMe->isType(typeid(TYPE));
    }

    template<typename TYPE>
    TYPE& get(){
        if(!mManageMe->isType(typeid(TYPE)))
            throw Exception_TypeFail();

        return dynamic_cast< Polymorph<TYPE>* >(mManageMe)->get();
    }

    template<typename TYPE>
    const TYPE& getConst() const {
        if(!mManageMe->isType(typeid(TYPE)))
            throw Exception_TypeFail();

        return dynamic_cast< Polymorph<TYPE>* >(mManageMe)->get();
    }

private:
    PolymorphInterface *mManageMe;
};


int main(){
    //- I'm using std lib containers the same way your container may work
    std::vector<Entry> magicContain;
    magicContain.push_back(
        Entry(new Entry::Polymorph<int>(42))
    );
    magicContain.push_back(
        Entry(new Entry::Polymorph<float>(1.5f))
    );
    magicContain.push_back(
        Entry(new Entry::Polymorph<std::string>("hi there."))
    );

    if(magicContain[0].isType<long>())
        long iShouldNotBeHere = 0;

    std::string impressYourFriends;
    if(magicContain[2].isType<std::string>())
        impressYourFriends = magicContain[2].get<std::string>();

    const std::vector<Entry> frozenMagic( magicContain );
    return frozenMagic[0].getConst<int>();
}

Upvotes: 1

Sam Varshavchik
Sam Varshavchik

Reputation: 118300

C++17 introduces std::any, a class that can store an arbitrary object, so you could simply use your container class to store std::any objects.

This is really just a C++ library addition, and it does not really make use of any core C++ language features, so before C++17 one can simply implement their own version of std::any.

This, however, is quite a bit of work. You will need to make use of advanced language features like placement new, for example. This is not something that can be explained in a few short paragraphs.

But another way it might be possible to do this without too much pain would be to take advantage of std::shared_ptr's type erasure features, and have your container store std::shared_ptr<void> objects. Your insertion function template could use make_shared to construct a std::shared_ptr to whatever type is being added to your container, then use std::static_pointer_cast<void> to convert it to a std::shared_ptr<void>, then store it in your container.

But then, of course, you would have to figure out the type of each object stored in your container, which can get quite painful.

And if all the different classes your container can store are all subclasses of the same base class, then you can simply shove std::shared_ptr<BaseClass> in your container, and call it a day. That would be the more straightforward solution.

Upvotes: 2

Related Questions