views:

983

answers:

5

Hello,

I would like to know what is better to use in my situation and why. First of all I heard that using RTTI (typeid) is bad. Anyone could explain why? If I know exactly types what is wrong to compare them in a runtime? Furthermore is there any example how to use boost::type_of? I have found none searching through the mighty google :) Other solution for me is specialization, but I would neet to specialize at least 9 types of new method. Here is an example what I need:

I have this class

  template<typename A, typename B, typename C>
  class CFoo
  {
     void foo()
     {
       // Some chunk of code depends on old A type
     }

  }

So I need to rather check in typeid(what is I heard is BAD) and make these 3 realizations in example like:

 void foo()
   {
      if (typeid(A) == typeid(CSomeClass)
       // Do this chunk of code related to A type
      else
      if (typeid(B) == typeid(CSomeClass)
       // Do this chunk of code related to B type
      else
      if (typeid(C) == typeid(CSomeClass)
       // Do this chunk of code related to C type
   }

So what is the best solution? I don't want to specialize for all A,B,C, because every type is has 3 specializations so I will get 9 methods or just this typeid check.

+3  A: 

It's bad because

  1. A, B and C are known at compile-time but you're using a runtime mechanism. If you invoke typeid the compiler will make sure to include metadata into the object files.
  2. If you replace "Do this chunk of code related to A type" with actual code that makes use of CSomeClass's interface you'll see you won't be able to compile the code in case A!=CSomeClass and A having an incompatible interface. The compiler still tries to translate the code even though it is never run. (see example below)

What you normally do is factoring out the code into separate function templates or static member functions of classes that can be specialized.

Bad:

template<typename T>
void foo(T x) {
    if (typeid(T)==typeid(int*)) {
        *x = 23; // instantiation error: an int can't be dereferenced
    } else {
        cout << "haha\n";
    }
}
int main() {
    foo(42); // T=int --> instantiation error
}

Better:

template<typename T>
void foo(T x) {
    cout << "haha\n";
}
void foo(int* x) {
    *x = 23;
}
int main() {
    foo(42); // fine, invokes foo<int>(int)
}

Cheers, s

sellibitze
+1  A: 

I think you've got your abstractions wrong somewhere.

I would try redefining A, B & C in terms of interfaces they need to expose (abstract base classes in C++ with pure virtual methods).

Templating allows basically duck-typing, but it sounds like CFoo knows too much about the A B & C classes.

typeid is bad because:

  1. typeid can be expensive, bloats binaries, carries around extra information that shouldn't be required.
  2. Not all compilers support it
  3. It's basically breaking the class hierarchy.

What I would recommend is refactoring: remove the templating, instead define interfaces for A, B & C, and make CFoo take those interfaces. That will force you to refactor the behaviour so the A, B & C are actually cohesive types.

Douglas Leeder
+3  A: 

Well generally solutions can be come up with without RTTI. It "can" show you haven't thought the design of the software out properly. THAT is bad. Sometimes RTTI can be a good thing though.

None-the-less there IS something odd in what you want to do. Could you not create an interim template designed something like as follows:

template< class T > class TypeWrapper
{
  T t;
public:
  void DoSomething()
  {
  }
};

then partially specialise for the functions you want to as follows:

template<> class TypeWrapper< CSomeClass >
{
  CSomeClass c;
public:
  void DoSomething()
  {
     c.DoThatThing();
  }
};

Then in your class define above you would do something such as ...

template

  class CFoo
  {
     TypeWrapper< A > a;
     TypeWrapper< B > b;
     TypeWrapper< C > c;
     void foo()
     {
       a.DoSomething();
       b.DoSomething();
       c.DoSomething();
     }

  }

This way it only actually does something in the "DoSomething" call if it is going through the partially specialised template.

Goz
+2  A: 

The problem lies in the code chunks you write for every specialization.

It doesn't matter if you write (lengthwise)

void foo()
{
   if (typeid(A) == typeid(CSomeClass)
    // Do this chunk of code related to A type
   else
   if (typeid(B) == typeid(CSomeClass)
    // Do this chunk of code related to B type
   else
   if (typeid(C) == typeid(CSomeClass)
    // Do this chunk of code related to C type
}

or

void foo()
{
   A x;
   foo_( x );
   B y;
   foo_( y );
   C z;
   foo_( z );
}
void foo_( CSomeClass1& ) {}
void foo_( CSomeClass2& ) {}
void foo_( CSomeClass3& ) {}

The upside of the second case is, when you add a class D, you get reminded by the compiler that there is an overload for foo_ missing which you have to write. This can be forgotten in the first variant.

Christopher
+2  A: 

I'm afraid this is not going to work in the first place. Those "chunks of code" have to be compilable even if the type is not CSomeClass.

I don't think type_of is going to help either (if it is the same as auto and decltype in C++0x).

I think you could extract those three chunks into separate functions and overload each for CSomeClass. (Edit: oh there are else if's. Then you might indeed need lots of overloads/specialization. What is this code for?)

Edit2: It appears that your code is hoping to do the equivalent of the following, where int is the special type:

#include <iostream>

template <class T>
bool one() {return false; }

template <>
bool one<int>() { std::cout << "one\n"; return true; }

template <class T>
bool two() {return false; }

template <>
bool two<int>() { std::cout << "two\n"; return true; }

template <class T>
bool three() {return false; }

template <>
bool three<int>() { std::cout << "three\n"; return true; }

template <class A, class B, class C>
struct X
{
    void foo()
    {
        one<A>() || two<B>() || three<C>();
    }
};

int main()
{
    X<int, double, int>().foo(); //one
    X<double, int, int>().foo();  //two
    X<double, double, double>().foo(); //...
    X<double, double, int>().foo(); //three
}
UncleBens