Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I store and access a type dynamically in c++?

I'm aware of c++ templates, which allow you to write code for multiple types, but what if I want to store and access a type dynamically? Why is this so difficult to do in c++?

I would very much prefer to not have to do something like this:

enum SupportedTypes
{
    IntType,
    FloatType,
    StringType
}

template <typename T>
class ClassThing
{
    public:
        T Value;
        SupportedTypes Type;
}

...

//Not sure if you could even access thing->Type, but regardless, you get the idea...
switch (thing->Type)
{
    case IntType:
        DoSomething(((ClassThing<int>*)thing)->T);
        break;
    case FloatType:
        DoSomething(((ClassThing<float>*)thing)->T);
        break;
    case StringType:
        DoSomething(((ClassThing<string>*)thing)->T);
        break;
}

Why doesn't c++ support something like this:

int whatIsThis = 5;
type t = typeid(whatIsThis); //typeid exists, but you can't do...:
t anotherInt = 5;

?

Another question that I have that I'm more optimistic of receiving a good answer to: if you choose to go the templated route, is there any way to maintain the type if you store it generically in a collection? E.g.:

vector<ClassThing> things;

(This will give an "argument list for class template ... is missing" error, by the way.) My guess is that no, this is not possible because the above is not possible.

like image 238
Andrew Avatar asked May 11 '15 04:05

Andrew


2 Answers

How do I store and access a type dynamically in c++?

There are many options to pick from:

  • use runtime polymorphism, where you have a base class that might offer some common functionality and derived classes for each supported type; you often have to make some choices about how "fat" your interface should be (providing base class functions that only work meaningfully for a subset of derived types) vs. forcing the client to use dynamic_cast<> to recover/switch-on the runtime type

    • a particularly powerful technique is having the derived classes be type-specific instantiations of the same template, as it means you can support arbitrary types parametrically, i.e. if they provide the semantics of usage that the template expects
  • use a discriminated union (basically, a type identification enum/int alongside a union of the supported types) - std::variant<> is a good choice for this

  • when creating/storing a value capture you'll necessarily know it's type

    • you can record both its typeinfo and address, then when accessing the variable later you can use the typeinfo to test whether the object is of a specific type - trying each supported type until a match is found - std::any<> is a good choice for this, or

    • you can capture an arbitrary set of type-specific operations using function pointers or std::function<>

Why doesn't c++ support something like this:

int whatIsThis = 5;
type t = typeid(whatIsThis); //typeid exists, but you can't do...:
t anotherInt = 5;?

It does, with decltype and auto:

int whatIsThis = 5;
using t = decltype(whatIsThis);
t anotherInt = 5;

auto anotherWhatever = whatIsThis; // another way to create an additional
                                   // variable of the same type

For runtime polymorphism, you might actually want to read up on factories (which create one of many types of object - all derived from some base interface - given some runtime input), and clone functions (which create a copy of a variable of unknown runtime type).

if you choose to go the templated route, is there any way to maintain the type if you store it generically in a collection: vector<ClassThing> things; (This will give an "argument list for class template ... is missing" error, by the way.)

You can't create even a single object from a template without instantiating it, so no there's no way to have an entire vector either. A reasonable approach is to derive the template from a base class and store [smart] pointers or std::reference_wrappers to the base class in the vector.

like image 129
Tony Delroy Avatar answered Nov 02 '22 23:11

Tony Delroy


int x = 5;
decltype(x) y = 4;
auto z = 3;

decltype(a) will give you the type of a. You can then use typedef to store the types, or other functions to remove references from the type if necessary.

For example:

typedef decltype(a) type1; 
type1 b = 2 * a;

auto makes you not need to specify the type at all.

The only thing you need is to compile in c++11 mode (-std=c++11) or later.

As for the vector question, decltype will work there too.

like image 44
coyotte508 Avatar answered Nov 03 '22 00:11

coyotte508