Search code examples
c++derived-classinitializer-list

compile-time initializing and runtime accessing an initializer_list vector containing derived elements


In the code below the initializer list is initialized with a B and a C object whose ctors pass in values “bbb” and 333 respectively. Since these objects derive from A, the list properly creates two A elements.

#include <string>
#include <vector>
#include <iostream>

struct A 
{
  char ch;
};

struct B : A 
{ 
  B( std::string str ) : str( str ) {}
  std::string str;
};

struct C : A 
{ 
  C( int num ) : num( num ) {}
  int num;
};

struct D
{
  D( std::initializer_list< A* > initializerList )
    : variadicVect( initializerList )
  {}

  std::vector< A* > variadicVect;
};

int main()
{
  D d { new B { "bbb" }, new C { 333 } };
  d.variadicVect[ 0 ]->ch = 'm'; // ok, but accesses base member
  //std::cout << d.variadicVect[ 0 ]->str << std::endl; // error C2039: 'str': is not a member of 'A'
  //std::cout << d.variadicVect[ 1 ]->num << std::endl; // error C2039: 'num': is not a member of 'A'

  return 0;
}

A similar problem was reported in link resolved by @Jarod42. He identified Object Slicing as the problem. To avoid this assignment-by-value issue, I new’d the initializer list object, but am still unable to access the derived class.

Another attempt I tried was to templatize the initializer_list type (code not shown), but I got this error: ‘D::variadicVect': only static data member templates are allowed’

My goal is to be able to initialize the list with various derived objects choosing the derived type at compile time and yet be able to access them properly at runtime. Is that possible?


Solution

  • To make your code compile you would have to cast your vector elements to appropriate types:

    std::cout << static_cast<B*>(d.variadicVect[ 0 ])->str << std::endl;
    std::cout << static_cast<C*>(d.variadicVect[ 1 ])->num << std::endl;
    

    otherwise C++ sees them as type A and only interface from A is available.

    Other solution is to add virtual functions (virtual std::string Str(){return "";} and virtual int Num(){return 0;} ) to A and override them in base classes, where needed. Then you will not have to cast. example below:

    http://coliru.stacked-crooked.com/a/ed7c73a5d8afa5e9

    #include <string>
    #include <vector>
    #include <iostream>
    
    struct A 
    {
      char ch;
      virtual std::string Str() { return ""; }
      virtual int Num() { return 0; }
    };
    
    struct B : A 
    { 
      B( std::string str ) : str( str ) {}
      std::string str;
      std::string Str() override { return str; }
    };
    
    struct C : A 
    { 
      C( int num ) : num( num ) {}
      int num;
      int Num() override { return num; }
    };
    
    struct D
    {
      D( std::initializer_list< A* > initializerList )
        : variadicVect( initializerList )
      {}
    
      std::vector< A* > variadicVect;
    
    };
    
    int main()
    {
      D d { new B { "bbb" }, new C { 333 } };
      d.variadicVect[ 0 ]->ch = 'm'; // ok, but accesses base member
      std::cout << d.variadicVect[ 0 ]->Str() << std::endl;
      std::cout << d.variadicVect[ 1 ]->Num() << std::endl;
    
      return 0;
    }