Fog Creek Software
g
Discussion Board




C++ question

Please excuse me if this is stupid question, but I am really stuck.

// This is a library class. I can’t change it
class A {
    public:
        …..
};

// This is my own class. I can do whatever I want with it.
class B : public A {
    public:
        std::string name() const { return m_name; }
        …..
    protected:
        std::string m_name;
};

I want to implement function ‘std::string name( T& x )’ which will invoke name() method if B is passed and return “unknown” if A is passed.
I know this can be implemented in runtime using RTTI. Is there other solution?
Templates, specialization anyone?

dk
Monday, October 13, 2003

Would composition help? That is, do you really need B to be a subclass of A, or could you duplicate A's interface, have B contain an A, and forward calls to B to it's contained A? If so then you can make two functions, one taking an A and one taking a B and it will all "just work".


Monday, October 13, 2003

Yes, it would help, but for specific A and B. Unfortunately
there are A1...An and B1...Bn which sometimes have name() method and sometimes not.

dk
Monday, October 13, 2003

would something along this line fit ?

string GetName( const A *pA)
{
  const B *pB= dynamic_cast<const B*>(pA);
  if (pA==NULL)
    return string("unknown");
  else
    return pB->GetName();
}

(of course, RTTI needs to be turned on).

Serge Wautier
Monday, October 13, 2003

You can do something like this:

string get_name(A * a)
{
  B * b = dynamic_cast<B*>(a);
  if (b == NULL)
    return "unknown";
  else
    return b->name();
}

I use here the fact that dynamic_cast<> will return NULL if it can't properly downcast from A to B (i.e. what you passed is not a pointee to B).

whatever
Monday, October 13, 2003

Dang simultaneous posting!!!

whatever
Monday, October 13, 2003

Does your code have to pass references to As around, or can it get away with just passing Bs?

If it's the latter, you could create two constructors for B. One takes a string and sets m_name to it. The other one doesn't and sets m_name to "Unknown". (Both, obviously, call down to A's constructor as well). The single implementation of Name() would then suffice.

Better Than Being Unemployed...
Monday, October 13, 2003

here's a short answer to your original question (tested on gcc 2.95):

template<typename T>
std::string name( const T& t ) {
  return "unknown";
}

template<>
std::string name<B>( const B& t ) {
  return t.name();
}

However this may get unmanageable if you have lots of classes that you need to specialize name() for. Maybe you should consider specializing the function name() for an interface instead of a specific subclass of A....

Benety Goh
Monday, October 13, 2003

The template-based solution will only work if the type can be determined staticly:

A a;
B b;
A& aref = a;
A& bref = b;

name(a); //calls unspecialised version
name(b); //calls specialised version
name(aref); //calls unspecialised version
name(bref); //calls unspecialised version ! oh no!

Even though bref refers to a B the type of bref is A& not B& and so the unspecialised (wrong) version is called.
The solution is either to use RTTI in the form of dynamic_cast, use explicit RTTI (which is the same as dynamic_cast really, but more fiddly and with more opportunity for error, so I wouldn't recommend it), or possibly some weird kludges which are essentially dynamic_cast by the back door.

In some cases providing a version taking B& can be handy as an optimisation, essentially:

std::string name(const B& x){
  return x.name();
}
std::string name(const A& x){
  const B* pt = dynamic_cast<const B*>(&x);
  if (pt)
    return name(*pt);
  return "unknown";
}

In this case there would be little advantage - but if you had a more complicated typing test to perform (say trying a dozen possible casts) and the more specific case might be called in a loop, then it may be worth considering.

Jon Hanna
Thursday, October 23, 2003

*  Recent Topics

*  Fog Creek Home