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?

Andrew Tomazos
  • 66,139
  • 40
  • 186
  • 319
  • @JoachimPileborg: In this toy example I could just replace `f` with `struct D { virtual void f() { g(t) } }`, but this misses the larger problem. – Andrew Tomazos Dec 24 '12 at 03:58

3 Answers3

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.

Potatoswatter
  • 134,909
  • 25
  • 265
  • 421
  • So I guess I could use `unordered_map` with `type_info::hash_code` as the key and a lambda that does the relevant cast and call to `g`. – Andrew Tomazos Dec 24 '12 at 03:59
  • @AndrewTomazosFathomlingCorps That would work. A template functor would save you from writing multiple lambdas. Use `std::size_t` instead of `int`, though. – Potatoswatter Dec 24 '12 at 04:02
  • Downvoting because this isn't a solution, and it's highly misleading: "In C++03 it was impossible" is just rubbish for "it" = creating code with the intended effect. For that's trivial to do, as shown in my answer, the only solution presented here so far. Which currently, as often happens on SO, has a negative vote tally. – Cheers and hth. - Alf Jan 21 '18 at 09:02
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).

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
-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.

Cheers and hth. - Alf
  • 142,714
  • 15
  • 209
  • 331
  • anonymous downvoter, you failed to inform others about *which* user it is that's so incompetent. yes, it does inform SO readers that the voting system in SO does not promote technical correctness, and that's good. so your vote wasn't totally wasted, but it would have been even better to know who you are. – Cheers and hth. - Alf Jan 21 '18 at 08:56
  • 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 ... – Maël Nison Jul 29 '18 at 13:28
  • I doubt the downvote was due to the reaction to the downvote. – Cheers and hth. - Alf Jul 29 '18 at 14:20
  • 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. – Cheers and hth. - Alf Jul 29 '18 at 14:28
  • 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. – daedsidog Oct 26 '22 at 23:33