9

I'm working with a class library where all classes are, directly or indirectly, derived from a base class Base and have a name. The library provides a facility to search for objects by a name, which will return a Base*.

Is there any way to find the type of the returned object without checking all possibilities using dynamic_casts as I did in the following example? I'd like to avoid that, if at all possible, since the derived classes have template parameters, which makes for quite a few possibilities.

It would also be fine if I were at least able to find out the class type (T1 or T2, in the example below) without knowing the template type, ie. do something like a dynamic_cast<T1<i_dont_care>*>.

#include <iostream>

using namespace std;

class Base {
public:
    virtual ~Base() {}
};

template <typename T> class T1 : public Base {};
template <typename T> class T2 : public Base {};

Base *find_by_name() {
    return new T2<int>();
}

int main() {
    Base *b = find_by_name();

    if (T1<int> *p = dynamic_cast<T1<int>*>(b)) {
        cout << "T1<int>" << endl;
        // do something meaningful with p
    } else if (T1<double> *p = dynamic_cast<T1<double>*>(b))
        cout << "T1<double>" << endl;
    else if (T2<int> *p = dynamic_cast<T2<int>*>(b))
        cout << "T2<int>" << endl;
    else if (T2<double> *p = dynamic_cast<T2<double>*>(b))
        cout << "T2<double>" << endl;
    else
        cout << "unknown" << endl;

    delete b;

    return 0;
}

Note that the above example is simplified, ie. in each if I'd do something meaningful with p.

I do realize that this is bad design from the very start, however I'm stuck with this library and there's also no way for me to change its implementation.

rainer
  • 6,769
  • 3
  • 23
  • 37
  • If this functionality is desired then `b` should have a member for doing it. Also you could try inspecting `typeid(*b).name()`. – M.M Aug 25 '16 at 07:09
  • your `p` are all redundant, write `if ( dynamic_cast*>(b) )` – M.M Aug 25 '16 at 07:09
  • Does `Base` really have no other members? This seems like the kind of functionality the library should provide. There is only so much you can do with a plain pointer. – juanchopanza Aug 25 '16 at 07:10
  • @M.M: I'll have to think about the `typeid` idea, thanks. Regarding the redundant `p`s, it seems my example was a bit too simplified :). – rainer Aug 25 '16 at 07:13
  • 1
    Return a `Base*`... Man, I thought we were through with the freaking Cylons. – user4581301 Aug 25 '16 at 07:13
  • @juanchopanza: I thought so too, however unfortunately it doesn't... – rainer Aug 25 '16 at 07:13
  • @user4581301: had to think about it for a while, but thanks for the laugh! :) – rainer Aug 25 '16 at 07:14
  • @user4581301 all your `Base*` are belong to us – M.M Aug 25 '16 at 07:16
  • @M.M `typeid(*b).name()` is not guaranteed to be anything in particular. – n. m. could be an AI Aug 25 '16 at 07:17
  • What is your desired syntax? "in each if I'd do something meaningful with p" --- like, for example, what exactly? "It would also be fine if I were at least able to find out the class type (T1 or T2, in the example below) without knowing the template type, ie. do something like a `dynamic_cast*>`." --- this is absolutely impossible. – n. m. could be an AI Aug 25 '16 at 07:21
  • @n.m. In essence I'd call one function of the library if the type is `T1>` and another one if it the type is `T2>`. – rainer Aug 25 '16 at 07:27
  • Pretty much all you can do with types at runtime is comparing them - "does this object have this type?" and "does this object have the same type as that object?". You can't refer to "the dynamic type of this object" and do anything with that. – molbdnilo Aug 25 '16 at 07:31
  • What would the signature of that function look like? It cannot depend on T1 or T2. There's no way to express `T1>` in C++. You can try parsing `typeid(*b).name()` but this is not portable. – n. m. could be an AI Aug 25 '16 at 07:36
  • @n.m. To be honest, I'm still trying to figure out what's going on in the functions I'd be calling. There's a mixture of macros, templated functions, etc that I have yet to understand... – rainer Aug 25 '16 at 07:39
  • X-Y solution: Can you insert a `SubBase` from which your extension classes inherit? You can have one cast to find out if you're dealing with one of your classes, then call the function you want and let inheritance take care of the rest. If it's not one of your classes, well, sucks to be you. – user4581301 Aug 25 '16 at 07:48
  • @user4581301 unforunately not, I can't touch the class library :(. – rainer Aug 25 '16 at 07:58
  • @n.m. I think it's guaranteed to be different for different types? – M.M Aug 25 '16 at 08:01
  • Not quite what I mean. `class SubBase:public Base` and `SubBase` declares all the helper functions you need. Your classes implement `SubBase`. No need for identifiers; just call the functions in `SubBase`, if the `Base` is a `SubBase`. – user4581301 Aug 25 '16 at 08:09
  • @M.M Nope. The literally only thing about `typeid::name()` in the standard is that it can be converted to `wstring`. No equality or inequality promise of any kind. – n. m. could be an AI Aug 25 '16 at 08:11
  • @user4581301 Unfortunately this is not possible since also the derived classes are provided by the library. As I said, the whole thing is a design nightmare to begin with ;). – rainer Aug 25 '16 at 11:02

2 Answers2

7

There's something like typeid http://en.cppreference.com/w/cpp/language/typeid, which applied to polymorphic expression will evaluate in a runtime to its type representation.

Following wiki example: https://en.wikipedia.org/wiki/Run-time_type_information#dynamic_cast

#include <iostream> 
#include <typeinfo>    // for 'typeid'

class Person {
public:
   virtual ~Person() {}
};

class Employee : public Person {
};

int main() 
{
    Person person;
    Employee employee;
    Person* ptr = &employee;
    Person& ref = employee;
    // The string returned by typeid::name is implementation-defined

    // Person (statically known at compile-time)
    std::cout << typeid(person).name() << std::endl;   

    // Employee (statically known at compile-time)
    std::cout << typeid(employee).name() << std::endl; 

    // Person* (statically known at compile-time)
    std::cout << typeid(ptr).name() << std::endl;      

    /* Employee (looked up dynamically at run-time
     * because it is the dereference of a
     * pointer to a polymorphic class) */
    std::cout << typeid(*ptr).name() << std::endl;     

    // Employee (references can also be polymorphic)        
    std::cout << typeid(ref).name() << std::endl;      
}
underscore_d
  • 6,309
  • 3
  • 38
  • 64
SzymonPajzert
  • 692
  • 8
  • 18
6

There is a typeid operator, which returns an instance of std::type_info, with which you can get the name of the type.

Not sure if that will help you though. First, the returned name is not guaranteed to be the same across implementations. Second - what would you do once you have the name? You'd compare it with your pre-defined names probably, but that is probably slower than a bunch of dynamic_cast's.

Without type support built into your Base class or a new intermediate layer of hierarchy, dynamic_cast is your best choice. In reality it will be very fast (usually just a single compare instruction).

By intermediate layer I mean:

class Base {
public:
    virtual ~Base() {}
};

class T1Base : public Base {};
class T2Base : public Base {};

template <typename T> class T1 : public T1Base {};
template <typename T> class T2 : public T2Base {};

int main() {
    Base *b = find_by_name();

    if (dynamic_cast<T1Base*>(b))
        cout << "T1" << endl;
    else if (dynamic_cast<T2Base*>(b))
        cout << "T2" << endl;
    else
        cout << "unknown" << endl;

    delete b;

    return 0;
}
rustyx
  • 80,671
  • 25
  • 200
  • 267
  • Accepting this answer as it points out the best alternatives, even if it seems that none of those really help me in this particular case. A galore of `dynamic_cast`s it is... – rainer Aug 25 '16 at 11:07
  • This is not an answer, this is a case between known class types. – Codebeat Jan 20 '19 at 06:57