6

I am trying to work on a project that will require me to determine a polymorphic object's type at runtime, so that I can cast it. An example of what I mean:

class A{
};
class B: public A{
    public:
        void foo(){
            printf("B::foo()\n");
        }
};

Later, I will have a bunch of B objects that are essentially stored as such:

std::vector<A*> v;
v.push_back(new B());

And I will need to call certain overloaded methods defined as:

void bar(B* b){
    b->foo();
}

After passing in objects that are stored in v. The problem that I am having is that in my actual use-case, I don't know the type of B at compile-time, so I can't just call bar by saying bar((B*)v.get(0));

The solution I have been thinking I might need is to somehow determine the type that each object is at runtime, so that I can cast it before passing it to bar.

The solution I have tried so far was to use decltype, but it didn't work for me because it just returns the static type of the value passed in, not the type at runtime.

Also, I do not want to use third party libraries for this project, since I would like to make it as small as possible.

Thank you for your help.

Edit:

I don't think I was clear enough in what I meant when I was describing my problem. In my actual use case (which is a bit long to try posting here, though I can post parts of it if necessary), I am trying to write a library in which there is a base class (represented here as A) which can be extended by the user into their own custom classes (represented here as B). In my example, B::foo() is simply supposed to represent how each subclass of A can have its own data members that are handled later by some method (represented in my example as bar). This is also the reason why A cannot simply have some virtual foo method.

The main issue I'm having is, since B is supposed to be user-defined, I don't know what it is at compile-time, but I do at link-time (since linking comes later). That is why the use of dynamic_cast (as suggested by Sam and Remy) won't work, because from what I understand of it, it requires that I know every possibility of what B could be, which I don't. Although it did look very close to what could work for me. If there was some way to get all possible subclasses (say, with preprocessor macros or templates), then I think this might be able to work.

I hope I've explained it better this time. Thank you again for your help.

Edit 2: Another clarifying point: In the actual project I am working on, I only want the user to be required to write their own B classes and overload bar to work with their custom classes. However, I don't want the user to be required to call bar. Rather, I would like to have bar be called from a base class (that I define). That is the main reason for even attempting to do this the way I am. That, and to see if I can.

Hope that clears everything up. Thank you again.

AFlyingCar
  • 83
  • 3
  • 7
  • Possible duplicate of [Why do we need Virtual Methods in C++?](http://stackoverflow.com/questions/2391679/why-do-we-need-virtual-methods-in-c) – Ryan Haining May 26 '16 at 00:18
  • From your edits, it appears that the correct way is still `virtual` members to `class A`, which provide full access to the data members to be present in any derived classes. Such access can include run-time type information etc. – Walter May 27 '16 at 17:13

4 Answers4

4

This is what virtual methods are for.

Declare the foo() virtual method in A, and you don't have to worry about doing ugly casts.

Alternatively, if A has at least one virtual function -- and a virtual destructor will do -- you can try your luck with a dynamic_cast<>.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
3

In C++, (dynamic) polymorphism is the tool for this type of thing.

struct A {
  virtual void foo() = 0;
  virtual~A() {}
};

struct B : A {
  void foo() override;
};

std::vector<std::unique_ptr<A>> vA;
vA.emplace_back(new B);
vA[0]->foo();

There is no need to find the actual type of the object pointed to by vA[0], the virtual function call mechanism will find the correct function (in this case B::foo()). Note the virtual destructor of class A. It guarantees that derived objects (of class B) are correctly deleted from a pointer to A (as kept by unique_ptr<A>).

Of course, there is also the dynamic_cast<>, which allows you to test whether the object is actually of a certain type:

auto ptr = dynamic_cast<B*>(vA[0].get());
if(ptr)   // object is a B
  std::cout<<" have B";
Walter
  • 44,150
  • 20
  • 113
  • 196
  • 1
    I think the usage of `unique_ptr` in this otherwise great answer is out of place. It is not what I would teach a beginner because of its surprising side effects. If you must use a smart pointer then use a `shared_ptr` instead as it behaves more like someone would expect a pointer to behave. No +1 from me. – Fozi May 26 '16 at 00:20
  • 2
    I disagree with the comment above. `unique_ptr` is what one should use by default to hold dynamically allocated objects. Owning raw pointers are out of the question, and `shared_ptr`s are overkill until you actually need their added complexity. – Quentin May 26 '16 at 07:52
  • @Fozi My thinking was pretty much along the lines of Quentin's comment. I'm not sure what 'surprising side effects' you're referring to. IMHO, `shared_ptr` is sensible only if ownership is to be shared (and more than one such pointer is potentially created for a single object). This is not the case here. – Walter May 27 '16 at 17:05
  • The surprising side effect is that if you assign it to something then it is not in the list any more. This is simply not how pointers work and given that beginners already have a hard time grasping the way pointers work we should keep it as simple as possible. – Fozi May 28 '16 at 02:03
  • I agree that in real code `unique_ptr` are useful but I just don't think that they should be used in code targeted at beginners. However, I don't agree that `shared_ptr`s add any relevant overhead. So far they never showed up in any of my profiles, and I use both (though I prefer `shared_ptr` whenever in doubt) – Fozi May 28 '16 at 02:07
  • @Fozi *if you assign it to something then it is not in the list any more* That would be indeed surprising, but thanks to the gods of C++ this is not possible (unless you write devious code): you cannot copy `unique_ptr`, i.e. code like `auto tmp = vA[0];` (in my example) will simply not compile. You have to say `auto tmp = std::move(vA[0]);` which is an entirely different thing and hardly done mistakenly by a beginner. – Walter May 29 '16 at 17:22
  • @Walter So then a question pops up with `why is this not working` on SO and someone will answer with `you have to use std::move`. I'm seeing this all the time. Why not just let them use `shared_ptr`s until they are ready to understand the (IMHO questionable) benefits of `unique_ptr`? – Fozi May 31 '16 at 14:05
2

I think this will work for you. First we define a pure virtual member function call_bar so that we can call it on the base class A. For the implementation we need the right type but with CRTP the user class can specify it for us.

class A
{
public:
  virtual void call_bar() = 0;
  virtual ~A() {}
};

template <typename T>
class B : public A
{
public:
  virtual void call_bar() override
  {
    bar(static_cast<T*>(this));
  }
};

class C: public B<C>
{
};
Drek
  • 148
  • 1
  • 2
  • 9
  • This technique seems like it will definitely work for me. The only problem I'm having is that when I try to create a vector of `A` objects, my compiler tells me that I have to specify the template parameters. The full error is: `template argument 1 is invalid` on the line I have: `typedef std::vector a_list;`. Will this technique still work if I say `std::vector > a_list;`? – AFlyingCar May 26 '16 at 22:38
  • `A` shouldn't be a template did you skip `B`? Here is a sample usage in [Ideone](https://ideone.com/x58ml0) @AFlyingCar – Drek May 26 '16 at 23:20
  • My apologies. I had modified your solution slightly to fit in my project better, but when doing so I accidentally applied it to `B` instead of `A` (without noticing). I've fixed that now though. Thank you again for your help. – AFlyingCar May 27 '16 at 00:41
0

The solution I have been thinking I might need is to somehow determine the type that each object is at runtime, so that I can cast it before passing it to bar.

dynamic_cast is what you are looking for:

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

class B: public A
{
public:
  void foo() {
    printf("B::foo()\n");
  }
};

...

std::vector<A*> v;
v.push_back(new B);

...

A *a = v.get(0);
B *b = dynamic_cast<B*>(a);
if (b)
    bar(b);
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770