Overload method for unique_ptr and shared_ptr is ambiguous with polymorphism
up vote
19
down vote
favorite
Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.
To reiterate the relevant bits and make this self contained, with the least details possible:
- I have a hierarchy of objects inheriting from
Interface
of which there areFoo
s andBar
s; - I have a
Scene
which owns these objects;
Foo
s are to beunique_ptr
s andBar
s are to beshared_ptr
s in my main (for reasons explained in the previous question);- the
main
passes them to theScene
instance, which takes ownership.
Minimal code example is this:
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Uncommenting the commented lines results in:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptr
s).
I'm passing both by-value because I want to make clear I'm taking ownership in Scene
(and upping the reference counter for the shared_ptr
s). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.
c++ c++14 overloading shared-ptr unique-ptr
add a comment |
up vote
19
down vote
favorite
Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.
To reiterate the relevant bits and make this self contained, with the least details possible:
- I have a hierarchy of objects inheriting from
Interface
of which there areFoo
s andBar
s; - I have a
Scene
which owns these objects;
Foo
s are to beunique_ptr
s andBar
s are to beshared_ptr
s in my main (for reasons explained in the previous question);- the
main
passes them to theScene
instance, which takes ownership.
Minimal code example is this:
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Uncommenting the commented lines results in:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptr
s).
I'm passing both by-value because I want to make clear I'm taking ownership in Scene
(and upping the reference counter for the shared_ptr
s). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.
c++ c++14 overloading shared-ptr unique-ptr
Converting fromstd::unique_ptr<Foo>
tostd::shared_ptr<Scene>
is as good as converting tostd::unique_ptr<Scene>
.
– felix
Nov 26 at 12:49
2
Nice MCVE, nice question. I'd like to see more of those.
– YSC
Nov 26 at 12:52
1
For me if class accepts two different kind of pointers for the same base class is a code smell. This means that something bad happens with memory management inside that class. So to fix it I would remove one of the methods. Probably theunique_ptr
version is obsolete since there is easy conversion toshared_ptr
. Other way to fix the issue is do not use overload (rename at least one of the methods) what should improve readability.
– Marek R
Nov 26 at 13:19
add a comment |
up vote
19
down vote
favorite
up vote
19
down vote
favorite
Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.
To reiterate the relevant bits and make this self contained, with the least details possible:
- I have a hierarchy of objects inheriting from
Interface
of which there areFoo
s andBar
s; - I have a
Scene
which owns these objects;
Foo
s are to beunique_ptr
s andBar
s are to beshared_ptr
s in my main (for reasons explained in the previous question);- the
main
passes them to theScene
instance, which takes ownership.
Minimal code example is this:
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Uncommenting the commented lines results in:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptr
s).
I'm passing both by-value because I want to make clear I'm taking ownership in Scene
(and upping the reference counter for the shared_ptr
s). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.
c++ c++14 overloading shared-ptr unique-ptr
Coding stuff after taking the hint from my previous question's answer, I ran into an issue with overloading Scene::addObject.
To reiterate the relevant bits and make this self contained, with the least details possible:
- I have a hierarchy of objects inheriting from
Interface
of which there areFoo
s andBar
s; - I have a
Scene
which owns these objects;
Foo
s are to beunique_ptr
s andBar
s are to beshared_ptr
s in my main (for reasons explained in the previous question);- the
main
passes them to theScene
instance, which takes ownership.
Minimal code example is this:
#include <memory>
#include <utility>
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface
{
};
class Bar : public Interface
{
};
class Scene
{
public:
void addObject(std::unique_ptr<Interface> obj);
// void addObject(std::shared_ptr<Interface> obj);
};
void Scene::addObject(std::unique_ptr<Interface> obj)
{
}
//void Scene::addObject(std::shared_ptr<Interface> obj)
//{
//}
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
// auto bar = std::make_shared<Bar>();
// scn->addObject(bar);
}
Uncommenting the commented lines results in:
error: call of overloaded 'addObject(std::remove_reference<std::unique_ptr<Foo, std::default_delete<Foo> >&>::type)' is ambiguous
scn->addObject(std::move(foo));
^
main.cpp:27:6: note: candidate: 'void Scene::addObject(std::unique_ptr<Interface>)'
void Scene::addObject(std::unique_ptr<Interface> obj)
^~~~~
main.cpp:31:6: note: candidate: 'void Scene::addObject(std::shared_ptr<Interface>)'
void Scene::addObject(std::shared_ptr<Interface> obj)
^~~~~
Uncommenting the shared and commenting the unique stuff also compiles, so I take it the problem is, like the compiler says, in the overload. However I need the overload as both these types will need to be stored in some kind of collection, and they are indeed kept as pointers to base (possibly all moved into shared_ptr
s).
I'm passing both by-value because I want to make clear I'm taking ownership in Scene
(and upping the reference counter for the shared_ptr
s). Not really clear to me where the issue lies at all, and I couldn't find any example of this elsewhere.
c++ c++14 overloading shared-ptr unique-ptr
c++ c++14 overloading shared-ptr unique-ptr
asked Nov 26 at 11:42
Laboratorio Cobotica
1357
1357
Converting fromstd::unique_ptr<Foo>
tostd::shared_ptr<Scene>
is as good as converting tostd::unique_ptr<Scene>
.
– felix
Nov 26 at 12:49
2
Nice MCVE, nice question. I'd like to see more of those.
– YSC
Nov 26 at 12:52
1
For me if class accepts two different kind of pointers for the same base class is a code smell. This means that something bad happens with memory management inside that class. So to fix it I would remove one of the methods. Probably theunique_ptr
version is obsolete since there is easy conversion toshared_ptr
. Other way to fix the issue is do not use overload (rename at least one of the methods) what should improve readability.
– Marek R
Nov 26 at 13:19
add a comment |
Converting fromstd::unique_ptr<Foo>
tostd::shared_ptr<Scene>
is as good as converting tostd::unique_ptr<Scene>
.
– felix
Nov 26 at 12:49
2
Nice MCVE, nice question. I'd like to see more of those.
– YSC
Nov 26 at 12:52
1
For me if class accepts two different kind of pointers for the same base class is a code smell. This means that something bad happens with memory management inside that class. So to fix it I would remove one of the methods. Probably theunique_ptr
version is obsolete since there is easy conversion toshared_ptr
. Other way to fix the issue is do not use overload (rename at least one of the methods) what should improve readability.
– Marek R
Nov 26 at 13:19
Converting from
std::unique_ptr<Foo>
to std::shared_ptr<Scene>
is as good as converting to std::unique_ptr<Scene>
.– felix
Nov 26 at 12:49
Converting from
std::unique_ptr<Foo>
to std::shared_ptr<Scene>
is as good as converting to std::unique_ptr<Scene>
.– felix
Nov 26 at 12:49
2
2
Nice MCVE, nice question. I'd like to see more of those.
– YSC
Nov 26 at 12:52
Nice MCVE, nice question. I'd like to see more of those.
– YSC
Nov 26 at 12:52
1
1
For me if class accepts two different kind of pointers for the same base class is a code smell. This means that something bad happens with memory management inside that class. So to fix it I would remove one of the methods. Probably the
unique_ptr
version is obsolete since there is easy conversion to shared_ptr
. Other way to fix the issue is do not use overload (rename at least one of the methods) what should improve readability.– Marek R
Nov 26 at 13:19
For me if class accepts two different kind of pointers for the same base class is a code smell. This means that something bad happens with memory management inside that class. So to fix it I would remove one of the methods. Probably the
unique_ptr
version is obsolete since there is easy conversion to shared_ptr
. Other way to fix the issue is do not use overload (rename at least one of the methods) what should improve readability.– Marek R
Nov 26 at 13:19
add a comment |
5 Answers
5
active
oldest
votes
up vote
14
down vote
accepted
The problem you are encountering is this constructor of shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.
This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).
This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointer
b)
U
is not an array type
c) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene
stores shared_ptr<Interface>
s. In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call.
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
@snake_style in that case, you don't have a template constructor selected forunique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions
– Caleth
Nov 26 at 13:10
add a comment |
up vote
4
down vote
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene
:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Full compilable example is here.
add a comment |
up vote
3
down vote
You have declared two overloads, one taking std::unique_ptr<Interface>
and one taking std::shared_ptr<Interface>
but are passing in a parameter of type std::unique_ptr<Foo>
. As none of your functions match directly the compiler has to perform a conversion to call your function.
There is one conversion available to std::unique_ptr<Interface>
(simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface>
(change to a shared pointer to the base class). These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous.
If you pass std::unique_ptr<Interface>
or std::shared_ptr<Interface>
there is no conversion required so there is no ambiguity.
The solution is to simply remove the unique_ptr
overload and always convert to shared_ptr
. This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate.
add a comment |
up vote
0
down vote
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Can you overload in terms of pointer-to-Foo
and pointer-to-Bar
instead of pointer-to-Interface
, since you want to treat them differently ?
add a comment |
up vote
0
down vote
The solution by jrok is already quite good. The following approach allows to reuse code even better:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "uniquen"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "sharedn"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers.
add a comment |
5 Answers
5
active
oldest
votes
5 Answers
5
active
oldest
votes
active
oldest
votes
active
oldest
votes
up vote
14
down vote
accepted
The problem you are encountering is this constructor of shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.
This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).
This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointer
b)
U
is not an array type
c) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene
stores shared_ptr<Interface>
s. In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call.
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
@snake_style in that case, you don't have a template constructor selected forunique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions
– Caleth
Nov 26 at 13:10
add a comment |
up vote
14
down vote
accepted
The problem you are encountering is this constructor of shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.
This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).
This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointer
b)
U
is not an array type
c) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene
stores shared_ptr<Interface>
s. In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call.
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
@snake_style in that case, you don't have a template constructor selected forunique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions
– Caleth
Nov 26 at 13:10
add a comment |
up vote
14
down vote
accepted
up vote
14
down vote
accepted
The problem you are encountering is this constructor of shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.
This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).
This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointer
b)
U
is not an array type
c) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene
stores shared_ptr<Interface>
s. In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call.
The problem you are encountering is this constructor of shared_ptr
(13), (which is not explicit), is as good a match as a similar "moving derived to base" constructor of unique_ptr
(6) (also not explicit).
template< class Y, class Deleter >
shared_ptr( std::unique_ptr<Y,Deleter>&& r ); // (13)
13) Constructs a
shared_ptr
which manages the object currently managed byr
. The deleter associated withr
is stored for future deletion of the managed object.r
manages no object after the call.
This overload doesn't participate in overload resolution if
std::unique_ptr<Y, Deleter>::pointer
is not compatible withT*
. Ifr.get()
is a null pointer, this overload is equivalent to the default constructor (1). (since C++17)
template< class U, class E >
unique_ptr( unique_ptr<U, E>&& u ) noexcept; //(6)
6) Constructs a
unique_ptr
by transferring ownership fromu
to*this
, whereu
is constructed with a specified deleter (E).
This constructor only participates in overload resolution if all of the following is true:
a)
unique_ptr<U, E>::pointer
is implicitly convertible to pointer
b)
U
is not an array type
c) Either
Deleter
is a reference type andE
is the same type asD
, orDeleter
is not a reference type andE
is implicitly convertible toD
In the non polymorphic case, you are constructing a unique_ptr<T>
from a unique_ptr<T>&&
, which uses the non-template move constructor. There overload resolution prefers the non-template
I'm going to assume that Scene
stores shared_ptr<Interface>
s. In that case you don't need to overload addObject
for unique_ptr
, you can just allow the implicit conversion in the call.
edited Nov 26 at 13:36
answered Nov 26 at 11:53
Caleth
15.3k22037
15.3k22037
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
@snake_style in that case, you don't have a template constructor selected forunique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions
– Caleth
Nov 26 at 13:10
add a comment |
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
@snake_style in that case, you don't have a template constructor selected forunique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions
– Caleth
Nov 26 at 13:10
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
Why it's not ambiguous without polymorphism?
– snake_style
Nov 26 at 12:55
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
@snake_style clarified. Move constructor was a misnomer
– Caleth
Nov 26 at 12:57
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
I mean struct D { }; void func(shared_ptr<D> ptr){} void func(unique_ptr<D> ptr){} int main() { auto p = make_unique<D>(); func(std::move(p)); } Why those ctor don't called here in this sample?
– snake_style
Nov 26 at 13:03
@snake_style in that case, you don't have a template constructor selected for
unique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions– Caleth
Nov 26 at 13:10
@snake_style in that case, you don't have a template constructor selected for
unique_ptr
, but the non-template move constructor. Overload resolution prefers non-template conversions– Caleth
Nov 26 at 13:10
add a comment |
up vote
4
down vote
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene
:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Full compilable example is here.
add a comment |
up vote
4
down vote
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene
:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Full compilable example is here.
add a comment |
up vote
4
down vote
up vote
4
down vote
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene
:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Full compilable example is here.
The other answer explains the ambiguity and a possible solution. Here's another way in case you end up needing both overloads; you can always add another parameter in such cases to break the ambiguity and use tag-dispatching. The boiler-plate code is hidden in private part of Scene
:
class Scene
{
struct unique_tag {};
struct shared_tag {};
template<typename T> struct tag_trait;
// Partial specializations are allowed in class scope!
template<typename T, typename D> struct tag_trait<std::unique_ptr<T,D>> { using tag = unique_tag; };
template<typename T> struct tag_trait<std::shared_ptr<T>> { using tag = shared_tag; };
void addObject_internal(std::unique_ptr<Interface> obj, unique_tag);
void addObject_internal(std::shared_ptr<Interface> obj, shared_tag);
public:
template<typename T>
void addObject(T&& obj)
{
addObject_internal(std::forward<T>(obj),
typename tag_trait<std::remove_reference_t<T>>::tag{});
}
};
Full compilable example is here.
answered Nov 26 at 12:34
jrok
44.5k685119
44.5k685119
add a comment |
add a comment |
up vote
3
down vote
You have declared two overloads, one taking std::unique_ptr<Interface>
and one taking std::shared_ptr<Interface>
but are passing in a parameter of type std::unique_ptr<Foo>
. As none of your functions match directly the compiler has to perform a conversion to call your function.
There is one conversion available to std::unique_ptr<Interface>
(simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface>
(change to a shared pointer to the base class). These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous.
If you pass std::unique_ptr<Interface>
or std::shared_ptr<Interface>
there is no conversion required so there is no ambiguity.
The solution is to simply remove the unique_ptr
overload and always convert to shared_ptr
. This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate.
add a comment |
up vote
3
down vote
You have declared two overloads, one taking std::unique_ptr<Interface>
and one taking std::shared_ptr<Interface>
but are passing in a parameter of type std::unique_ptr<Foo>
. As none of your functions match directly the compiler has to perform a conversion to call your function.
There is one conversion available to std::unique_ptr<Interface>
(simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface>
(change to a shared pointer to the base class). These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous.
If you pass std::unique_ptr<Interface>
or std::shared_ptr<Interface>
there is no conversion required so there is no ambiguity.
The solution is to simply remove the unique_ptr
overload and always convert to shared_ptr
. This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate.
add a comment |
up vote
3
down vote
up vote
3
down vote
You have declared two overloads, one taking std::unique_ptr<Interface>
and one taking std::shared_ptr<Interface>
but are passing in a parameter of type std::unique_ptr<Foo>
. As none of your functions match directly the compiler has to perform a conversion to call your function.
There is one conversion available to std::unique_ptr<Interface>
(simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface>
(change to a shared pointer to the base class). These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous.
If you pass std::unique_ptr<Interface>
or std::shared_ptr<Interface>
there is no conversion required so there is no ambiguity.
The solution is to simply remove the unique_ptr
overload and always convert to shared_ptr
. This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate.
You have declared two overloads, one taking std::unique_ptr<Interface>
and one taking std::shared_ptr<Interface>
but are passing in a parameter of type std::unique_ptr<Foo>
. As none of your functions match directly the compiler has to perform a conversion to call your function.
There is one conversion available to std::unique_ptr<Interface>
(simple type conversion to unique pointer to base class) and another to std::shared_ptr<Interface>
(change to a shared pointer to the base class). These conversions have the same priority so the compiler doesn't know which conversion to use so your functions are ambiguous.
If you pass std::unique_ptr<Interface>
or std::shared_ptr<Interface>
there is no conversion required so there is no ambiguity.
The solution is to simply remove the unique_ptr
overload and always convert to shared_ptr
. This assumes that the two overloads have the same behaviour, if they don't renaming one of the methods could be more appropriate.
answered Nov 26 at 13:21
Alan Birtles
7,665733
7,665733
add a comment |
add a comment |
up vote
0
down vote
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Can you overload in terms of pointer-to-Foo
and pointer-to-Bar
instead of pointer-to-Interface
, since you want to treat them differently ?
add a comment |
up vote
0
down vote
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Can you overload in terms of pointer-to-Foo
and pointer-to-Bar
instead of pointer-to-Interface
, since you want to treat them differently ?
add a comment |
up vote
0
down vote
up vote
0
down vote
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Can you overload in terms of pointer-to-Foo
and pointer-to-Bar
instead of pointer-to-Interface
, since you want to treat them differently ?
Foos are to be unique_ptrs and Bars are to be shared_ptrs in my main (for reasons explained in the previous question);
Can you overload in terms of pointer-to-Foo
and pointer-to-Bar
instead of pointer-to-Interface
, since you want to treat them differently ?
answered Nov 26 at 14:25
WaffleSouffle
2,32522223
2,32522223
add a comment |
add a comment |
up vote
0
down vote
The solution by jrok is already quite good. The following approach allows to reuse code even better:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "uniquen"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "sharedn"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers.
add a comment |
up vote
0
down vote
The solution by jrok is already quite good. The following approach allows to reuse code even better:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "uniquen"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "sharedn"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers.
add a comment |
up vote
0
down vote
up vote
0
down vote
The solution by jrok is already quite good. The following approach allows to reuse code even better:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "uniquen"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "sharedn"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers.
The solution by jrok is already quite good. The following approach allows to reuse code even better:
#include <memory>
#include <utility>
#include <iostream>
#include <type_traits>
namespace internal {
template <typename S, typename T>
struct smart_ptr_rebind_trait {};
template <typename S, typename T, typename D>
struct smart_ptr_rebind_trait<S,std::unique_ptr<T,D>> { using rebind_t = std::unique_ptr<S>; };
template <typename S, typename T>
struct smart_ptr_rebind_trait<S,std::shared_ptr<T>> { using rebind_t = std::shared_ptr<S>; };
}
template <typename S, typename T>
using rebind_smart_ptr_t = typename internal::smart_ptr_rebind_trait<S,std::remove_reference_t<T>>::rebind_t;
class Interface
{
public:
virtual ~Interface() = 0;
};
inline Interface::~Interface() {}
class Foo : public Interface {};
class Bar : public Interface {};
class Scene
{
void addObject_internal(std::unique_ptr<Interface> obj) { std::cout << "uniquen"; }
void addObject_internal(std::shared_ptr<Interface> obj) { std::cout << "sharedn"; }
public:
template<typename T>
void addObject(T&& obj) {
using S = rebind_smart_ptr_t<Interface,T>;
addObject_internal( S(std::forward<T>(obj)) );
}
};
int main(int argc, char** argv)
{
auto scn = std::make_unique<Scene>();
auto foo = std::make_unique<Foo>();
scn->addObject(std::move(foo));
auto bar = std::make_shared<Bar>();
scn->addObject(bar); // ok
}
What we do here is to first introduce some helper classes that allow to rebind smart pointers.
answered Nov 26 at 15:03
Handy999
913
913
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Some of your past answers have not been well-received, and you're in danger of being blocked from answering.
Please pay close attention to the following guidance:
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53480362%2foverload-method-for-unique-ptr-and-shared-ptr-is-ambiguous-with-polymorphism%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Converting from
std::unique_ptr<Foo>
tostd::shared_ptr<Scene>
is as good as converting tostd::unique_ptr<Scene>
.– felix
Nov 26 at 12:49
2
Nice MCVE, nice question. I'd like to see more of those.
– YSC
Nov 26 at 12:52
1
For me if class accepts two different kind of pointers for the same base class is a code smell. This means that something bad happens with memory management inside that class. So to fix it I would remove one of the methods. Probably the
unique_ptr
version is obsolete since there is easy conversion toshared_ptr
. Other way to fix the issue is do not use overload (rename at least one of the methods) what should improve readability.– Marek R
Nov 26 at 13:19