-1

Is it possible to maintain knowledge of the derived class in any std c++ container without using pointers, dynamically casting return values from the container? I know I can create a vector say, of pointers of some base class type and have them retain their sub classes. But the question is do I have to use pointers?

Example:

struct A {
  int x = 0, y = 0, z = 0;
  virtual void foo() { cout << "A" << endl; };
};

struct B : public A {
  int a = 1, b = 1, c = 1;
  virtual void foo() { cout << "B" << endl; };
};

int main() {  
  <SOMECONTAINER><A> a(2);
  a[0] = A();
  a[1] = B();
  B * p;
  B& n = dynamic_cast<B&>(a[1]); // Always throws?
  p = dynamic_cast<B*>(&a[1]); // Always zero?
  cout << p << endl;
}
Francis M. Bacon
  • 665
  • 7
  • 20
  • It is possible to avoid using indirections e.g. with variant data types; it is also possible to waste less space by storing the object size alongside each element and dynamically go to the next element when iterating (not supported by Standard Library containers). – dyp Jun 18 '14 at 23:07
  • 1
    I strongly recommend watching [Inheritance is the Base Class of Evil](http://channel9.msdn.com/Events/GoingNative/2013/Inheritance-Is-The-Base-Class-of-Evil). Sean Parent describes how you can achieve something analogous to `std::vector v{Cat{}, Dog{}}; v[0].makeSound(); v[1].makeSound();` printing `Meow! Woof!`. The `Cat` and `Dog` classes in question would only need a function (member or free is up to you) named `makeSound` that takes that class as a parameter. The classes are not derived from `Animal`. – ghostofstandardspast Jun 18 '14 at 23:19
  • @dyp Ah OK I suspected such a thing was possible, and was what the "variant" types of the various c++ frameworks do. But such is just not part of std c++ containers. – Francis M. Bacon Jun 18 '14 at 23:35
  • 1
    @ghostofstandardspast If I remember correctly Sean's approach still relies on using virtual dispatch and dynamic allocation. (If my recollection is incorrect, just tell me I'm an idiot.) He just uses holder classes to hide it as an implementation detail. That approach is very nice, but if the goal is to avoid dynamic allocation it comes up short. – Chris Hayden Jun 18 '14 at 23:40
  • 1
    You can define a (restricted) variant data type that works with C++ containers. What you cannot do however is to put objects of different sizes in a Standard Library container. (Restricted) variant data types are typically implemented as unions and therefore have the size of the largest possible type they can contain. – dyp Jun 18 '14 at 23:40
  • 1
    @ChrisHayden, Yes, there's still the use of virtual functions hidden away, but as far as I can tell, the OP just wants to be able to have a container of "base class" objects instead of pointers. – ghostofstandardspast Jun 18 '14 at 23:44
  • The standard says you can cast a pointer and you can copy a struct, but you can't cast a struct. A union would be the only pointer-less way. – david.pfx Jun 19 '14 at 06:25

5 Answers5

5

Yes, you do have to use pointers. Otherwise, attempting to put a B into a container of A results in slicing: the B gets cut down into an A (this is not limited to containers, the exact same thing happens if you do A a = B() or if you pass a B to a function expecting an A).

When you later take it back out, it's an A that has absolutely no knowledge its lineage includes an illustrious forefather of type B -- and no matter what way you look at an A, you can't make it a B.

Community
  • 1
  • 1
Jon
  • 428,835
  • 81
  • 738
  • 806
  • 1
    More info: you can use smart pointers, to avoid some of the pitfalls of plain pointers. References maintain polymorphic behaviour too, but you can't have a container of references. – M.M Jun 18 '14 at 23:08
  • 1
    What about using something like `boost::variant`? No need for pointers. – Deduplicator Jun 18 '14 at 23:21
  • @Deduplicator: Using `boost::variant` to do what exactly? Get polymorphic behavior? – Jon Jun 18 '14 at 23:26
  • 2
    Store an object of any applicable (sub-)class without using pointers, as the OP wanted. – Deduplicator Jun 18 '14 at 23:28
  • @Deduplicator: It's not the same thing. You have to know the set of possible types at compile time. – Jon Jun 18 '14 at 23:30
  • 2
    Well, one can easily code a containter for objects derived from the same base object, which have a specified maximum size and alignment-requirement, without writing down all the types which can be held. That would knock that objection down (mostly). – Deduplicator Jun 18 '14 at 23:49
  • @Deduplicator: Sure, but IMO this question is of a far simpler scope. – Jon Jun 18 '14 at 23:54
  • 2
    @jon but someone might wonder if it is possible and find this question, so why not answer it? ;) – Yakk - Adam Nevraumont Jun 19 '14 at 01:04
1

I am going to ignore alignment, or rather assume that data after a pointer is sufficiently aligned.

template<class T, unsigned N>
struct poly_anna;
template<class T,unsigned N>
struct poly_bob {
  typedef poly_anna<T,N> poly_anna_;
  T*(*get)(poly_anna_*) = nullptr;
  void(*destroy)(poly_anna_*) = nullptr;
  void(*move_to)(poly_anna_ *,poly_anna_*) = nullptr;
  void(*copy_to)(poly_anna_ const*, poly_anna_*)=nullptr;
};

template<class T, unsigned N>
struct poly_anna {
private:
  poly_bob<T,N> const*bob=nullptr;
  char buff[N];
public:
  template<class U> static poly_bob<T,N> const* get_bob() {
    static poly_bob<T,N> b={
      [](poly_anna*a)->T&{ return *(U*)&a->buff[0]; },
      [](poly_anna*a){ ((U*)&a->buff[0])->~U(); a->bob = nullptr; },
      [](poly_anna*s,poly_anna*d){
        if (s->bob==d->bob){
          *((U*)&d->buff[0])=std::move(*((U*)&d->buff[0]));
          return;
        }
        if (d->bob != nullptr) {
          d->bob->destroy(b);
        }
        d->store( std::move( *(U*)&s->buff[0] ) );
      },
      [](poly_anna const* s, poly_anna*d){
        if (d->bob == s->bob){
          *(U*)&d->buff[0] = *(U const*)&s->buff[0];
          return;
        }
        if (d->bob){ d->bob->destroy(d); }
        d->store( *(U const*)*s->buff[0] );
      }
    };
    return &b;
  };
  template<class U_>
  void store(U_&& u){
    typedef typename std::decay<U_>::type U;
    static_assert( sizeof(U)<=N, "N not large enough" );
    if (bob) bob->destroy( this );
    bob = get_bob<U>();
    new (&buff[0]) U( std::forward<U_>(u) );
  }
  void reset(){ if (bob) bob->destroy(this); }
  T& get() {
    return bob->get(this);
   }
  T const& get() const {
    return bob->get(const_cast<poly_anna*>(this));
   }
   poly_anna( poly_anna const& o ){
     if (o.bob) o.bob->copy_to( &o, this );
   }
   poly_anna( poly_anna && o ){
     if (o.bob) o.bob->move_to( &o, this );
   }
   poly_anna&operator=( poly_anna const& o ){
     if (o.bob) o.bob->copy_to( &o, this );
     else if (bob) bob->destroy(this);
     return *this
   }
   poly_anna&operator=( poly_anna && o ){
     if (o.bob) o.bob->move_to( &o, this );
     else if (bob) bob->destroy(this);
     return *this
   }
   poly_anna()=default;
   ~poly_anna(){if(bob)bob->destroy(this);}
   explicit operator bool()const{return bob;}
};

That is my attempt at a polymorphic variant. It stores T and children of T so long as they are no larger than N and can be stored in std containers.

Let me know if it compiles.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

You need pointers for virtual member function dispatch.

When you think about it, without pointers, you are left with "by value". Value semantics and and polymorphism don't really make sense together.

A value has a single context / type. It is atomic and simple. WYSIWIG, so to speak. Sure, you can cast it, but then you have ... another value.

codenheim
  • 20,467
  • 1
  • 59
  • 80
  • nitpick: You are, of course, correct within the confines of this question, but not globally. We don't "need pointers for virtual member function dispatch" as references work equally well as sources of virtual dispatch. One 'needs' pointers if polymorphism is to be achieved via collections of objects stored in containers, as containers cannot contain references. Many people who want virtual dispatch want to do it via a container, but saying pointers are strictly required for virtual dispatch is still reductive. not that you meant that :) – underscore_d Aug 14 '16 at 14:20
0

There's an oft-quoted programmer proverb: There is no problem which cannot be solved with an additional layer of indirection, except too many layers of indirection.

Putting it into practice, take a look at boost::variant.
If your container stores boost::variants allowing all the (sub-)classes you want to store, you can avoid pointers.

That can be a win, but need not be.
Measure before you commit to such a solution.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118
0

I think there are actually two questions here:

  1. Is it possible to get polymorphic semantics in an STL container without using pointers and the associated dynamic allocation?
  2. Is is possible to retain the concrete type of a polymorphic object stored in an STL container?

The answer to 1 is yes, with some effort. As some have mentioned, one way to do this is to use a variant type like boost::variant. The problem with that approach is that you lose the ability to interact naturally with the objects stored in the variant and instead have to write visitors, which have a lot of syntactic overhead.

If the class hierarchy is bounded then a better approach might be to use a variation on boost::variant (no pun intended) specifically designed to preserve polymorphic semantics and its associated syntax. One example might be the emplacer. As noted in dyp's comment above, emplacer is a restricted variant.

As for question 2, I'm not aware of any way to do that without using typeid() or a hand-rolled type system.

Chris Hayden
  • 1,104
  • 6
  • 6