I have the following
#include <iostream>
#include <memory>
template<typename _type>
class handle
{
using ptr = std::shared_ptr<_type>;
using pptr = std::shared_ptr<ptr>;
public:
handle(handle<_type> const & other) :
mData(make_pptr(*(other.mData)))
{}
handle(_type && data) :
mData(make_pptr(std::move(data)))
{}
private:
pptr mData;
template<typename ..._args>
constexpr auto make_ptr(_args && ...args)
{
return std::make_shared<_type>(std::forward<_args>(args)...);
}
constexpr auto make_pptr(ptr const & pointer)
{
return std::make_shared<ptr>(pointer);
}
template<typename ..._args>
constexpr auto make_pptr(_args && ...args)
{
return std::make_shared<ptr>(make_ptr(std::forward<_args>(args)...));
}
};
int main()
{
handle<int> h = 5;
handle<int> h2(h);
}
Compiled with g++-4.9 --std=c++14 -O0 -o main main.cpp
the code
handle<int> h2(h);
does not compile. The problem functions are all the overloads of
make_pptr
As I understand it, the template function will always be chosen, as the compiler tries to find the most specialized function call and the perfect forwarding creates exactly that.
I found the following two pages who seem to handle that problem with the type trait std::enable_if
and std::is_same
.
https://akrzemi1.wordpress.com/2013/10/10/too-perfect-forwarding/
http://www.codesynthesis.com/~boris/blog/2012/05/30/perfect-forwarding-and-overload-resolution/
The actual question is, how can I change this function, so that the non-template functions will be called if I pass the factory function an already existing pointer?
Is there a common way to do it?
As Jarod's answer explains, in the constructor
handle(handle<_type> const & other) :
mData(make_pptr(*(other.mData)))
{}
you call make_pptr
with an argument of type shared_ptr<_type>&
, which makes the perfect forwarding overload of make_pptr
a better match than the one that takes a shared_ptr<_type> const&
. You can cast the argument to const&
as he shows, or you could add another overload of make_pptr
that takes a non-const
lvalue reference.
constexpr auto make_pptr(ptr & pointer)
{
return std::make_shared<ptr>(pointer);
}
Yet another option is to constrain the perfect forwarding overload so that it is viable only when the first argument of the parameter pack is not a shared_ptr<_type>
.
Some helpers to evaluate whether the first type in the parameter pack is a shared_ptr<T>
namespace detail
{
template<typename... _args>
using zeroth_type = typename std::tuple_element<0, std::tuple<_args...>>::type;
template<typename T, bool eval_args, typename... _args>
struct is_shared_ptr
: std::false_type
{};
template<typename T, typename... _args>
struct is_shared_ptr<T, true, _args...>
: std::is_same<std::decay_t<zeroth_type<_args...>>,
std::shared_ptr<T>
>
{};
}
Then constrain the perfect forwarding make_pptr
as follows
template<typename ..._args,
typename = std::enable_if_t<
not detail::is_shared_ptr<_type, sizeof...(_args), _args...>::value
>
>
constexpr auto make_pptr(_args && ...args)
{
return std::make_shared<ptr>(make_ptr(std::forward<_args>(args)...));
}
I also had to change your make_ptr
overload because the way you have it defined in your example requires that _type
be constructible from nullptr
.
constexpr auto make_ptr()
{
return std::make_shared<_type>();
// no nullptr arg above, shared_ptr default ctor will initialize _type* to nullptr
}
Live demo
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With