Polymorphic behavior for pointer types?

In the following example, Composite::print() works on an array of Base object pointers.
Is there a way to make Composite::print() work on an array of Derived object pointers as well?

This example compiles and runs as expected, but uncommenting lines 37, 38, 39, or 40 gets compile errors:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <iostream>

class Base
{
    public:
        virtual void print() { std::cout << " Base"; }
};

class Derived : public Base
{
    public:
        virtual void print() { std::cout << " Derived"; }
};

class Composite
{
    private:
        Base *const *const ptrsBase; //array of Base pointers
    public:
        Composite( Base *const ptrsBase[]): ptrsBase(ptrsBase) {}
        void print()
        {
            ptrsBase[0]->print();
            ptrsBase[1]->print();
        }
};

Base b1;
Base b2;
Base* ptrsBase[] = {&b1, &b2};
Composite compBase(ptrsBase);

Derived d1;
Derived d2;
Derived* ptrsDer[] = {&d1, &d2};
/* these attempts did not compile:
ptrsBase = (Base*)ptrsDer;  //error: ‘ptrsBase’ does not name a type
ptrsBase = ptrsDer;         //error: ‘ptrsBase’ does not name a type
Base** ptrsDerB = ptrsDer;  //error: invalid conversion from ‘Derived**’ to ‘Base**’ [-fpermissive]
Composite compDer(ptrsDer); //error: invalid conversion from ‘Derived**’ to ‘Base* const*’ [-fpermissive]
*/

int main()
{                               //output:
    compBase.print();           // Base Base
    std::cout << std::endl;

    //compDer.print();
    std::cout << std::endl;
}


Thank you.
Last edited on
Apparently (as I just tested) you cant convert Derived** to Base** coz
pointer to Derived* is not the same as pointer to Base*

1
2
3
4
5
    Derived* dp = &d1;
    Base* bp = dp;             //fine
    
    Derived** dpp= &dp;
    Base** bpp = dpp;          //invalid conversion from 'Derived**' to 'Base**' 


To fix that you can just use
1
2
3
4
5
6
7
8
9
10
Base b1;
Base b2;
Base* ptrsBase[] = {&b1, &b2};
Composite compBase(ptrsBase);

Derived d1;
Derived d2;
Base* ptrsDer[] = {&d1, &d2};

Composite compDer(ptrsDer); 


Now they are both treated as pointer to Base*

This example is safe but be careful with array(of type derived) to pointer(base*) conversions. Here is the example from B.Stroustrups book page 763.

1
2
3
4
5
6
7
8
9
10
11
12
13
void maul(Shape* p, int n)  //Danger
{
    for(int i = 0; i!=n; ++i)
        p[i].draw();                         //looks innocent; its not
} 


void user()
{
    Circle image[10];                     //an image is composed of 10 Circles
    //...
    maul(image, 10);                     //"maul" 10 circles
}


Here is how these sizes of array elements are seen in these 2 functions

user() view : [ Image[0] ][ Image[1] ][ Image[2] ][ Image[3] ]
maul() view: [ p[0] ][ p[1] ][ p[2] ][ p[3] ]

So Circle and Shape objects occupy different sizes in memory (because of additional data members) and if you try to travel the array of Circles as if those were shapes you will get undefined behavior because you will be trying to read the information needed in the wrong memory location.
Last edited on
The problem would be same with
const std::vector<Base const *> ptrsBase;
but perhaps a bit more clear on casts (and vector knows its size).

On those implicit casts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
using BP = Base *;
using DP = Derived *;

BP * bpp;
DP * dpp;
bpp = dpp; // error: DP (a pointer type) does not inherit from BP (a pointer type)

BP bp;
DP dp;
bp = dp; // ok: Derived IS-A Base

vector<BP> bpv;
vector<DP> dpv;
bpv = dpv; // error
bpv.resize( dpv.size );
std::copy( dpv.begin(), dpv.end(), bpv.begin() ); // ok 
Last edited on
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <vector>
#include <type_traits>

struct base
{
    virtual ~base() = default ;
    virtual std::ostream& print( std::ostream& stm = std::cout ) const { return stm << "base" ; }
};

std::ostream& operator<< ( std::ostream& stm, const base& b ) { return b.print(stm) ; }

struct derived : base
{
    virtual std::ostream& print( std::ostream& stm = std::cout ) const override { return stm << "derived" ; }
};

struct composite
{
    std::vector<base*> ptrs_base ;

    template < typename ITERATOR >
    composite( ITERATOR begin, ITERATOR end,
               std::enable_if< std::is_convertible< typename std::iterator_traits<ITERATOR>::value_type, base* >::value >* = nullptr )
        : ptrs_base(begin,end) {}

    template < typename T >
    composite( T** pp, std::size_t n, std::enable_if< std::is_convertible< T*, base* >::value >* = nullptr )
        : ptrs_base( pp, pp+n ) {}

    void print( std::ostream& stm = std::cout ) const { for( auto p : ptrs_base ) if(p) stm << *p << '\n' ; }
};

int main()
{
    derived d1, d2, d3 ;
    derived* ptrs_derived[3] { std::addressof(d1), std::addressof(d2), std::addressof(d3) } ;

    composite c( std::begin(ptrs_derived), std::end(ptrs_derived) ) ;
    c.print() ;

    composite c2( ptrs_derived, 3 ) ;
    c2.print() ;
}

http://coliru.stacked-crooked.com/a/4462661a9d5aef78
Thanks etrusks. That's perfect; simple and safe for my application.
Topic archived. No new replies allowed.