Velocity Reviews

Velocity Reviews (http://www.velocityreviews.com/forums/index.php)
-   C++ (http://www.velocityreviews.com/forums/f39-c.html)
-   -   shared_ptr; derived classes; ambiguitity in overloaded functions (http://www.velocityreviews.com/forums/t632396-shared_ptr-derived-classes-ambiguitity-in-overloaded-functions.html)

Fokko Beekhof 08-26-2008 12:19 PM

shared_ptr; derived classes; ambiguitity in overloaded functions
 
Hello all,

please consider the following code:

--------------------------------------------------
#include <tr1/memory>

struct BaseA
{
int x;
};

struct BaseB
{
double x;
};


struct DerivA : public BaseA
{
int y;
};

struct DerivB : public BaseB
{
double y;
};

struct S
{
S(std::tr1::shared_ptr<BaseA> pa_) : pa(pa_) {}
S(std::tr1::shared_ptr<BaseB> pb_) : pb(pb_) {}

std::tr1::shared_ptr<BaseA> pa;
std::tr1::shared_ptr<BaseB> pb;
};

int main()
{
// S s(std::tr1::shared_ptr<BaseA>(new DerivA()) ); // works
// S s( new DerivA() ); // Doesn't work, SP constructor is explicit
S s(std::tr1::shared_ptr<DerivA>(new DerivA()) ); // breaks

return 0;
}
--------------------------------------------------

As you see, there are 3 possibilities in main(). #1 uses
std::tr1::shared_ptr<BaseA>, which works, like I would expect it to.
Then, #2 tries to pass a pointer where a shared_ptr is required, so that
doesn't work - I can understand that too, pointers cannot be implicitly
converted to shared_ptr.

My question:
Option #3, using a std::tr1::shared_ptr<DerivA>, does NOT work:
$ g++ -Wall ambiguousSP.cpp
ambiguousSP.cpp: In function ‘int main()’:
ambiguousSP.cpp:37: error: call of overloaded
‘S(std::tr1::shared_ptr<DerivA>)’ is ambiguous
ambiguousSP.cpp:28: note: candidates are: S::S(std::tr1::shared_ptr<BaseB>)
ambiguousSP.cpp:27: note: S:S(std::tr1::shared_ptr<BaseA>)

If I use "regular" pointers, it would works fine:
---------------------------------------------
struct BaseA
{
int x;
};

struct BaseB
{
double x;
};


struct DerivA : public BaseA
{
int y;
};

struct DerivB : public BaseB
{
double y;
};


struct S
{
S(BaseA * pa_) : pa(pa_), pb(0) {}
S(BaseB * pb_) : pa(0), pb(pb_) {}

BaseA * pa;
BaseB * pb;
};

int main()
{
S s(new DerivA()); // works fine
delete s.pa;

return 0;
}
---------------------------------------------

Could anyone comment on this ? Should option #3 with shared_ptr<DerivA>
work or not ?

Conceptually, I believe so. I can understand that the platform could
have some trouble with it, if it doesn't recognize that the template
parameter DerivA in shared_ptr<DerivA> is derived of BaseA.

Thanks for any comments,

F. Beekhof

Salt_Peter 08-26-2008 08:00 PM

Re: shared_ptr; derived classes; ambiguitity in overloaded functions
 
On Aug 26, 8:19 am, Fokko Beekhof <Fokko.Beek...@cui.unige.ch> wrote:
> Hello all,
>
> please consider the following code:
>
> --------------------------------------------------
> #include <tr1/memory>
>
> struct BaseA
> {
> int x;
>
> };
>
> struct BaseB
> {
> double x;
>
> };
>
> struct DerivA : public BaseA
> {
> int y;
>
> };
>
> struct DerivB : public BaseB
> {
> double y;
>
> };
>
> struct S
> {
> S(std::tr1::shared_ptr<BaseA> pa_) : pa(pa_) {}
> S(std::tr1::shared_ptr<BaseB> pb_) : pb(pb_) {}
>
> std::tr1::shared_ptr<BaseA> pa;
> std::tr1::shared_ptr<BaseB> pb;
>
> };
>
> int main()
> {
> // S s(std::tr1::shared_ptr<BaseA>(new DerivA()) ); // works
> // S s( new DerivA() ); // Doesn't work, SP constructor is explicit
> S s(std::tr1::shared_ptr<DerivA>(new DerivA()) ); // breaks
>
> return 0;}
>
> --------------------------------------------------
>
> As you see, there are 3 possibilities in main(). #1 uses
> std::tr1::shared_ptr<BaseA>, which works, like I would expect it to.
> Then, #2 tries to pass a pointer where a shared_ptr is required, so that
> doesn't work - I can understand that too, pointers cannot be implicitly
> converted to shared_ptr.
>
> My question:
> Option #3, using a std::tr1::shared_ptr<DerivA>, does NOT work:
> $ g++ -Wall ambiguousSP.cpp
> ambiguousSP.cpp: In function int main():
> ambiguousSP.cpp:37: error: call of overloaded
> S(std::tr1::shared_ptr<DerivA>) is ambiguous
> ambiguousSP.cpp:28: note: candidates are: S::S(std::tr1::shared_ptr<BaseB>)
> ambiguousSP.cpp:27: note: S:S(std::tr1::shared_ptr<BaseA>)
>
> If I use "regular" pointers, it would works fine:
> ---------------------------------------------
> struct BaseA
> {
> int x;
>
> };
>
> struct BaseB
> {
> double x;
>
> };
>
> struct DerivA : public BaseA
> {
> int y;
>
> };
>
> struct DerivB : public BaseB
> {
> double y;
>
> };
>
> struct S
> {
> S(BaseA * pa_) : pa(pa_), pb(0) {}
> S(BaseB * pb_) : pa(0), pb(pb_) {}
>
> BaseA * pa;
> BaseB * pb;
>
> };
>
> int main()
> {
> S s(new DerivA()); // works fine
> delete s.pa;
>
> return 0;}
>
> ---------------------------------------------
>
> Could anyone comment on this ? Should option #3 with shared_ptr<DerivA>
> work or not ?
>
> Conceptually, I believe so. I can understand that the platform could
> have some trouble with it, if it doesn't recognize that the template
> parameter DerivA in shared_ptr<DerivA> is derived of BaseA.
>
> Thanks for any comments,
>
> F. Beekhof


Since a conversion would be required...

S s(std::tr1::shared_ptr<DerivA>(new DerivA()) );

fails. This works:

struct S
{
...
S(std::tr1::shared_ptr<DerivA> pa_) : pa(pa_) {}
...
};

its the same as:

void f(unsigned u) { }
void f(char c) { }

int main()
{
f(0); // call of overload is ambiguous, 0 is of type int
}


Fokko Beekhof 08-28-2008 08:35 AM

Re: shared_ptr; derived classes; ambiguitity in overloaded functions
 
Salt_Peter wrote:
> On Aug 26, 8:19 am, Fokko Beekhof <Fokko.Beek...@cui.unige.ch> wrote:
>> Hello all,
>>
>> please consider the following code:
>>
>> --------------------------------------------------
>> #include <tr1/memory>
>>
>> struct BaseA
>> {
>> int x;
>>
>> };
>>
>> struct BaseB
>> {
>> double x;
>>
>> };
>>
>> struct DerivA : public BaseA
>> {
>> int y;
>>
>> };
>>
>> struct DerivB : public BaseB
>> {
>> double y;
>>
>> };
>>
>> struct S
>> {
>> S(std::tr1::shared_ptr<BaseA> pa_) : pa(pa_) {}
>> S(std::tr1::shared_ptr<BaseB> pb_) : pb(pb_) {}
>>
>> std::tr1::shared_ptr<BaseA> pa;
>> std::tr1::shared_ptr<BaseB> pb;
>>
>> };
>>
>> int main()
>> {
>> // S s(std::tr1::shared_ptr<BaseA>(new DerivA()) ); // works
>> // S s( new DerivA() ); // Doesn't work, SP constructor is explicit
>> S s(std::tr1::shared_ptr<DerivA>(new DerivA()) ); // breaks
>>
>> return 0;}
>>
>> --------------------------------------------------
>>
>> As you see, there are 3 possibilities in main(). #1 uses
>> std::tr1::shared_ptr<BaseA>, which works, like I would expect it to.
>> Then, #2 tries to pass a pointer where a shared_ptr is required, so that
>> doesn't work - I can understand that too, pointers cannot be implicitly
>> converted to shared_ptr.
>>
>> My question:
>> Option #3, using a std::tr1::shared_ptr<DerivA>, does NOT work:
>> $ g++ -Wall ambiguousSP.cpp
>> ambiguousSP.cpp: In function int main():
>> ambiguousSP.cpp:37: error: call of overloaded
>> S(std::tr1::shared_ptr<DerivA>) is ambiguous
>> ambiguousSP.cpp:28: note: candidates are: S::S(std::tr1::shared_ptr<BaseB>)
>> ambiguousSP.cpp:27: note: S:S(std::tr1::shared_ptr<BaseA>)
>>
>> If I use "regular" pointers, it would works fine:
>> ---------------------------------------------
>> struct BaseA
>> {
>> int x;
>>
>> };
>>
>> struct BaseB
>> {
>> double x;
>>
>> };
>>
>> struct DerivA : public BaseA
>> {
>> int y;
>>
>> };
>>
>> struct DerivB : public BaseB
>> {
>> double y;
>>
>> };
>>
>> struct S
>> {
>> S(BaseA * pa_) : pa(pa_), pb(0) {}
>> S(BaseB * pb_) : pa(0), pb(pb_) {}
>>
>> BaseA * pa;
>> BaseB * pb;
>>
>> };
>>
>> int main()
>> {
>> S s(new DerivA()); // works fine
>> delete s.pa;
>>
>> return 0;}
>>
>> ---------------------------------------------
>>
>> Could anyone comment on this ? Should option #3 with shared_ptr<DerivA>
>> work or not ?
>>
>> Conceptually, I believe so. I can understand that the platform could
>> have some trouble with it, if it doesn't recognize that the template
>> parameter DerivA in shared_ptr<DerivA> is derived of BaseA.
>>
>> Thanks for any comments,
>>
>> F. Beekhof

>
> Since a conversion would be required...
>
> S s(std::tr1::shared_ptr<DerivA>(new DerivA()) );
>
> fails. This works:
>
> struct S
> {
> ...
> S(std::tr1::shared_ptr<DerivA> pa_) : pa(pa_) {}
> ...
> };
>
> its the same as:
>
> void f(unsigned u) { }
> void f(char c) { }
>
> int main()
> {
> f(0); // call of overload is ambiguous, 0 is of type int
> }
>


Yes, but this:
struct S
{
...
S(std::tr1::shared_ptr<DerivA> pa_) : pa(pa_) {}
...
};
might work, but is conceptually completely different from the pointer
version:
struct S {
...
S(BaseA * pa_) : pa(pa_), pb(0) {}
...
};

because in the latter, there is polymorphism - any pointer to a derived
class is-a base class too. When I move the code to shared_ptrs, that breaks.

Shouldn't there be some sort of polymorphism-awareness in the template
arguments ? I.e., a shared_ptr<DerivA> is also a shared_ptr<BaseA> ?

Kai-Uwe Bux 08-28-2008 12:56 PM

Re: shared_ptr; derived classes; ambiguitity in overloaded functions
 
Fokko Beekhof wrote:

> Hello all,
>
> please consider the following code:
>
> --------------------------------------------------
> #include <tr1/memory>
>
> struct BaseA
> {
> int x;
> };
>
> struct BaseB
> {
> double x;
> };
>
>
> struct DerivA : public BaseA
> {
> int y;
> };
>
> struct DerivB : public BaseB
> {
> double y;
> };
>
> struct S
> {
> S(std::tr1::shared_ptr<BaseA> pa_) : pa(pa_) {}
> S(std::tr1::shared_ptr<BaseB> pb_) : pb(pb_) {}
>
> std::tr1::shared_ptr<BaseA> pa;
> std::tr1::shared_ptr<BaseB> pb;
> };
>
> int main()
> {
> // S s(std::tr1::shared_ptr<BaseA>(new DerivA()) ); // works
> // S s( new DerivA() ); // Doesn't work, SP constructor is explicit
> S s(std::tr1::shared_ptr<DerivA>(new DerivA()) ); // breaks
>
> return 0;
> }
> --------------------------------------------------
>
> As you see, there are 3 possibilities in main(). #1 uses
> std::tr1::shared_ptr<BaseA>, which works, like I would expect it to.
> Then, #2 tries to pass a pointer where a shared_ptr is required, so that
> doesn't work - I can understand that too, pointers cannot be implicitly
> converted to shared_ptr.
>
> My question:
> Option #3, using a std::tr1::shared_ptr<DerivA>, does NOT work:
> $ g++ -Wall ambiguousSP.cpp
> ambiguousSP.cpp: In function ?int main()?:
> ambiguousSP.cpp:37: error: call of overloaded
> ?S(std::tr1::shared_ptr<DerivA>)? is ambiguous
> ambiguousSP.cpp:28: note: candidates are:
> S::S(std::tr1::shared_ptr<BaseB>) ambiguousSP.cpp:27: note:
> S:S(std::tr1::shared_ptr<BaseA>)
>
> If I use "regular" pointers, it would works fine:
> ---------------------------------------------
> struct BaseA
> {
> int x;
> };
>
> struct BaseB
> {
> double x;
> };
>
>
> struct DerivA : public BaseA
> {
> int y;
> };
>
> struct DerivB : public BaseB
> {
> double y;
> };
>
>
> struct S
> {
> S(BaseA * pa_) : pa(pa_), pb(0) {}
> S(BaseB * pb_) : pa(0), pb(pb_) {}
>
> BaseA * pa;
> BaseB * pb;
> };
>
> int main()
> {
> S s(new DerivA()); // works fine
> delete s.pa;
>
> return 0;
> }
> ---------------------------------------------
>
> Could anyone comment on this ? Should option #3 with shared_ptr<DerivA>
> work or not ?


According to the technical report TR1, it should not work.


> Conceptually, I believe so. I can understand that the platform could
> have some trouble with it, if it doesn't recognize that the template
> parameter DerivA in shared_ptr<DerivA> is derived of BaseA.


You have a point, but it would require changing the specs of shared_ptr.

To simplify the exposition, let us consider the following class:

template < typename T >
struct pointer_to {

T * the_ptr;

pointer_to ( T * ptr )
: the_ptr ( ptr )
{}

T & operator* ( void ) const {
return ( *the_ptr );
}

T * operator-> ( void ) const {
return ( the_ptr );
}

template < typename D >
pointer_to ( pointer_to<D> const & d_pointer )
: the_ptr ( d_pointer.the_ptr )
{}

};

There is a conversion operator that allows to copy construct a pointer_to<T>
from a pointer to any derived class. Attempts to copy construct from
non-derived classes will fail when the compiler encounters the body of the
conversion operator. With this setup, the following will be ambiguous:

struct X {};
struct XD : public X {};
struct Y {};
struct YD : public Y {};

void f ( pointer_to<X> xp ) {}
void f ( pointer_to<Y> yp ) {}

int main ( void ) {
pointer_to<YD> ydp ( new YD );
f( ydp );
}

The reason is that the compiler sees two possible conversions and it is not
supposed to check whether only one of them can be compiled cleanly.


Now, there is a way to guide the compiler in these issues. But it requires
some serious scaffolding:

struct yes_type { char dummy; };
struct no_type { yes_type a; yes_type b; };

template < typename From, typename To >
class is_convertible {

static
From* dummy ( void );

static
yes_type check ( To );

static
no_type check ( ... );

public:

static bool const value =
sizeof( check( *dummy() ) ) == sizeof( yes_type );

}; // is_convertible

template < bool b, typename T >
struct enable_if;

template < typename T >
struct enable_if<true,T> { typedef T type; };



template < typename T >
struct pointer_to {

T * the_ptr;

pointer_to ( T * ptr )
: the_ptr ( ptr )
{}

T & operator* ( void ) const {
return ( *the_ptr );
}

T * operator-> ( void ) const {
return ( the_ptr );
}

template < typename D >
pointer_to ( pointer_to<D> const & d_pointer,
typename enable_if< is_convertible<D*,T*>::value, void* >::type
p = 0 )
: the_ptr ( d_pointer.the_ptr )
{}

};

With this setup, the above snippet will compile cleanly since the signature
of the conversion operator is enough to tell the compiler that there is
only one possible conversion.



Best

Kai-Uwe Bux


Fokko Beekhof 08-28-2008 03:09 PM

Re: shared_ptr; derived classes; ambiguitity in overloaded functions
 
>
>
>> Conceptually, I believe so. I can understand that the platform could
>> have some trouble with it, if it doesn't recognize that the template
>> parameter DerivA in shared_ptr<DerivA> is derived of BaseA.

>
> You have a point, but it would require changing the specs of shared_ptr.
>
> To simplify the exposition, let us consider the following class:
>
> template < typename T >
> struct pointer_to {
>
> T * the_ptr;
>
> pointer_to ( T * ptr )
> : the_ptr ( ptr )
> {}
>
> T & operator* ( void ) const {
> return ( *the_ptr );
> }
>
> T * operator-> ( void ) const {
> return ( the_ptr );
> }
>
> template < typename D >
> pointer_to ( pointer_to<D> const & d_pointer )
> : the_ptr ( d_pointer.the_ptr )
> {}
>
> };
>
> There is a conversion operator that allows to copy construct a pointer_to<T>
> from a pointer to any derived class. Attempts to copy construct from
> non-derived classes will fail when the compiler encounters the body of the
> conversion operator. With this setup, the following will be ambiguous:
>
> struct X {};
> struct XD : public X {};
> struct Y {};
> struct YD : public Y {};
>
> void f ( pointer_to<X> xp ) {}
> void f ( pointer_to<Y> yp ) {}
>
> int main ( void ) {
> pointer_to<YD> ydp ( new YD );
> f( ydp );
> }
>
> The reason is that the compiler sees two possible conversions and it is not
> supposed to check whether only one of them can be compiled cleanly.
>
>
> Now, there is a way to guide the compiler in these issues. But it requires
> some serious scaffolding:
>
> struct yes_type { char dummy; };
> struct no_type { yes_type a; yes_type b; };
>
> template < typename From, typename To >
> class is_convertible {
>
> static
> From* dummy ( void );
>
> static
> yes_type check ( To );
>
> static
> no_type check ( ... );
>
> public:
>
> static bool const value =
> sizeof( check( *dummy() ) ) == sizeof( yes_type );
>
> }; // is_convertible
>
> template < bool b, typename T >
> struct enable_if;
>
> template < typename T >
> struct enable_if<true,T> { typedef T type; };
>
>
>
> template < typename T >
> struct pointer_to {
>
> T * the_ptr;
>
> pointer_to ( T * ptr )
> : the_ptr ( ptr )
> {}
>
> T & operator* ( void ) const {
> return ( *the_ptr );
> }
>
> T * operator-> ( void ) const {
> return ( the_ptr );
> }
>
> template < typename D >
> pointer_to ( pointer_to<D> const & d_pointer,
> typename enable_if< is_convertible<D*,T*>::value, void* >::type
> p = 0 )
> : the_ptr ( d_pointer.the_ptr )
> {}
>
> };
>
> With this setup, the above snippet will compile cleanly since the signature
> of the conversion operator is enough to tell the compiler that there is
> only one possible conversion.
>
>
>
> Best
>
> Kai-Uwe Bux
>


That is quite impressive...

Anyway, to conclude:
- A solution to my problem is possible,
- but it not in the specs of shared_ptr.
- The specs of shared_ptr are probably not going to change anytime soon,
- and trying rewrite/override parts of the standard is not smart either.

Should I post this problem + solution anywhere so that maybe one day the
specs of shared_ptr will be improved to better reflect the behavior of
regular pointers ? If so, where ?

Many thanks,
Fokko Beekhof

Kai-Uwe Bux 08-29-2008 06:28 AM

Re: shared_ptr; derived classes; ambiguitity in overloaded functions
 
Fokko Beekhof wrote:

[snip]
[about how the constructors in shared_ptr<> can lead to ambiguous
conversion where raw pointer conversions would be unique.]
>
> Anyway, to conclude:
> - A solution to my problem is possible,
> - but it not in the specs of shared_ptr.
> - The specs of shared_ptr are probably not going to change anytime soon,
> - and trying rewrite/override parts of the standard is not smart either.
>
> Should I post this problem + solution anywhere so that maybe one day the
> specs of shared_ptr will be improved to better reflect the behavior of
> regular pointers ? If so, where ?


Well, there was comp.std.c++; but is has been defunct for a while. Some of
the discussion of standardization issues has since moved to the moderated
list comp.lang.c++.moderated.


Best

Kai-Uwe Bux


All times are GMT. The time now is 10:25 PM.

Powered by vBulletin®. Copyright ©2000 - 2014, vBulletin Solutions, Inc.
SEO by vBSEO ©2010, Crawlability, Inc.