C++11 Dynamic Cast If Else Chain -> Switch
Asked Answered
T

3

7

Consider the following:

struct B { };

template<typename T>
struct D : B
{
    T t;
}

void g(int i) { ... }
void g(string s) { ... }
void g(char c) { ... }

void f(B* b)
{
    if (dynamic_cast<D<int>*>(b))
    {
        g(dynamic_cast<D<int>*>(b)->t);
    }
    else if (dynamic_cast<D<string>*>(b))
    {
        g(dynamic_cast<D<string>*>(b)->t);
    }
    else if (dynamic_cast<D<char>*>(b))
    {
        g(dynamic_cast<D<char>*>(c)->t)
    }
    else
        throw error;
};

Here there are only three possible types of T - int, string, char - but if the list of possible types were longer, say n, the if else chain would take O(n) to execute.

One way to deal with this would be to store an extra type code in D somehow and then switch on the type code.

The RTTI system must already have such a code. Is there someway to get access to it and switch on it?

Or is there a better way to do what I'm trying to do?

Tieck answered 24/12, 2012 at 3:44 Comment(1)
@JoachimPileborg: In this toy example I could just replace f with struct D { virtual void f() { g(t) } }, but this misses the larger problem.Tieck
J
4

C++11 is almost there.

In C++03 it was impossible because the only way to get a compile-time constant (which case requires) was through the type system. Since typeid always returns the same type, it couldn't produce different alternatives for switch.

C++11 adds constexpr and type_info::hash_code as a unique identifier of types, but doesn't combine them. You can use typeid in a constant expression on a of type name or statically-typed expressions, but because hash_code is a non- constexpr function you cannot call it.

Of course there are various workarounds, one of which you describe, and the most general of which apply a visitor over a type vector using template metaprogramming.

Jared answered 24/12, 2012 at 3:51 Comment(2)
So I guess I could use unordered_map<int, void(B*)> with type_info::hash_code as the key and a lambda that does the relevant cast and call to g.Tieck
@AndrewTomazosFathomlingCorps That would work. A template functor would save you from writing multiple lambdas. Use std::size_t instead of int, though.Jared
T
3

Since only a few types are valid, you could solve this with virtual functions and template specialization instead:

struct B
{
    virtual void g() = 0;
}

template<typename T>
struct D : public B
{
    T t;
};

template<>
struct D<int> : public B
{
    int t;
    void g() { /* do something here */ }
};

template<>
struct D<std::string> : public B
{
    std::string t;
    void g() { /* do something here */ }
};

template<>
struct D<char> : public B
{
    char t;
    void g() { /* do something here */ }
};

void f(B* b)
{
    b->g();
}

This will fail at compile-time if you provide the wrong types, instead or requiring runtime checks (which C++ is quite bad at).

Tantalite answered 24/12, 2012 at 4:0 Comment(2)
The real problem has a large number of types valid.Tieck
@AndrewTomazosFathomlingCorps It can still be a feasible solution, using e.g. preprocessor macros to help with the class definitions.Tantalite
F
-2

The primary choice for run time switching on type in C++, is a virtual function.

It is dead simple:

#include <string>
#include <iostream>
using namespace std;

struct Base
{
    virtual void g() const = 0;
};

template< class Type > void g( Type const& );

template<> void g( int const& ) { cout << "int" << endl; }
template<> void g( string const& ) { cout << "string" << endl; }
template<> void g( char const& ) { cout << "char" << endl; }

template< class Type >
struct Derived: Base
{
    Type t;
    virtual void g() const override { ::g<Type>( t ); }
};

void f( Base& b ) { b.g(); }

int main()
{
    Derived<int>().g();
}

As you can it is also efficient, O(1) instead of the silly O(n). Plus, with static (compile time) type checking instead of dynamic (run time) type checking, saving a pretty annoying amount of testing. What more can I say? Really, forget about type code and enums and such. Remember that Bertrand Meyer chose to not support enums in Eiffel, for just this reason, that people tend to abuse them for type codes. Do use virtual functions.

Hey, virtual functions!

They're really useful when otherwise you'd want dynamic dispatch on type.

So, I recommend using virtual functions for that. :)


EDIT: templatized ::g in order to avoid possible ambiguities in the real code.

Fedak answered 24/12, 2012 at 4:47 Comment(3)
I'm not the downvoter, but your comment is extremely passive aggressive and doesn't cast a good light on SO. We all get downvotes sometimes, it's no reason to vent like this ...Ephemeris
By the way, your mention of "passive agressive": that's a term that indicates shying away from direct confrontation (please do look it up). I'm not known as such person and if anything the comment now looks quite confrontational. Of course it's now out of context.Fedak
Oftentimes a virtual function is not a solution in a case where your class is a wrapper for a pointer to data. In such a case, copying the class by by value would involve only copying the pointer, yet due to object slicing, virtual functions would not work, and you are forced to dynamic cast by default.Apostle

© 2022 - 2024 — McMap. All rights reserved.