1

I want to do runtime polymorphism and must know the actual type as well e.g. to count the instances of a certain derived class. Here is what I have so far:

header

struct Base {
  virtual ~Base();
};

struct DerivedFoo : public Base {};
struct DerivedBar : public Base {};

source

Base::~Base() {}

int main() {
  std::vector<std::unique_ptr<Base>> my_array{};
  my_array.emplace_back(std::make_unique<DerivedFoo>());
  my_array.emplace_back(std::make_unique<DerivedBar>());

  for (const auto& elem : my_array) {
    if (std::is_same<DerivedFoo, decltype(elem)>::value) {
      // do something
    }

    // some testing
    std::cout << typeid(decltype(elem)).name() << '\n';
    std::cout << typeid(decltype(*elem)).name() << '\n';
    std::cout << typeid(decltype(*elem.get())).name() << '\n';
  }

  return 0;
}

output

St10unique_ptrI4BaseSt14default_deleteIS0_EE
4Base
4Base
St10unique_ptrI4BaseSt14default_deleteIS0_EE
4Base
4Base

Problem is: I only manage to get the type of the base class. Not the derived one. So I never enter the if statement.

  • I could add a virtual function "GetID()" or similar. But my feeling is that this is redundant. I should be able to do this with type traits?
  • I looked into std::unique_pointer<...>::element_type and std::pointer_traits but could not get it running.

Any ideas?

Tano
  • 33
  • 5
  • What are you *really* trying to do? Why do you need to check if an element is of one type or another? Doing things like that is typically a sign of bad design. Can't your problem be solve through natural polymorphism and virtual functions? – Some programmer dude Nov 02 '19 at 12:17
  • `decltype(elem)` returns the type of the unique_ptr – Yamahari Nov 02 '19 at 12:20
  • @Someprogrammerdude I'm writing a chess engine. My base is a "Square" class. "King", "Queen", .., and "Empty" are derived classes. An additional "Board" class holds a member `std::array, 64> squares_;`. I got stuck at the very basic unit tests to make sure when default constructing my board all squares are of type "Empty". – Tano Nov 02 '19 at 12:38
  • 2
    @Someprogrammerdude The more I think about it, the more I feel you are right. Maybe I should simply add an "IsEmpty()" virtual function then. Still curious about an answer to the posted problem though. – Tano Nov 02 '19 at 12:50
  • Unit testing is kind of a special case were sometimes the normal "rules" have to be bent a little (and sometimes quite a lot). Perhaps a possible (and better than rule-bending) solution could be [*mocking*](https://stackoverflow.com/questions/2665812/what-is-mocking)? – Some programmer dude Nov 02 '19 at 13:39

2 Answers2

0

Using the visitor pattern

struct Counter;
struct Foo;
struct Bar;
struct BarBar;

struct Counter {
    int barCount = 0;
    int barBarCount = 0;

    void visit(Bar *bar) {
        barCount += 1;
    }
    void visit(BarBar *bar) {
        barBarCount += 1;
    }
};

struct Foo {
    virtual void accept(Counter* v) = 0;
};

struct Bar : public Foo {
    void accept(Counter* v) override {
        v->visit(this);
    }
};

struct BarBar : public Foo {
    void accept(Counter* v) override {
        v->visit(this);
    }
};


int main()
{
    std::vector<std::unique_ptr<Foo>> foos;
    foos.emplace_back(std::make_unique<Bar>());
    foos.emplace_back(std::make_unique<BarBar>());

    Counter counter;

    for(const auto& foo : foos) {
        foo->accept(&counter);
    }

    std::cout << counter.barCount << " " << counter.barBarCount << std::endl;
}

Output 1 1

Yamahari
  • 1,570
  • 5
  • 17
0

The problem in you code is that you expect runtime polymorphism but only use compile-time types:

  • decltype(x) gives you the compile-time type of x
  • so typeid(decltype(x)) refers to the runtime type information of the compile-time type, so 4Base in your example.

If you want to use the run-time type, use typeid directly on the object, as follows:

std::cout <<< typeid(elem).name() << '\n';
std::cout << typeid(*elem).name() << '\n';
std::cout << typeid(*elem.get()).name() << '\n';

The result would then look like:

St10unique_ptrI4BaseSt14default_deleteIS0_EE
10DerivedFoo
10DerivedFoo
St10unique_ptrI4BaseSt14default_deleteIS0_EE
10DerivedBar
10DerivedBar

In the same way, std::is_same<DerivedFoo, decltype(elem)>::value is based on template type deduction, so compile-time again. If you want to check this on a polymorphic type at run time, you need to use the following construct:

if (dynamic_cast<DerivedFoo*>(&*elem)) {
    // do something
}
Christophe
  • 54,708
  • 5
  • 52
  • 107