Question

I wrote a function template to "convert"/repack a boost::shared_ptr<T> to a std::shared_ptr<T> and vice versa by following this proposal. It's working fine unless I have a boost::shared_pt<T> and the type of T is an abstract class.

What I figured out so far is, that the problem occurs when boost/shared_ptr.hpp and boost/shared_array.hpp are included together. If only boost/shared_ptr.hpp is included it's working when the type of T is an abstract class.

I'm using clang 3.3 and boost 1.55.0 . It would be great if someone could tell my why it's not working and how to get it working.

Thanks for your help



Here is a minimal example:

//main.cpp

#include <boost/shared_array.hpp> //removing this include and it's working
#include <boost/shared_ptr.hpp>
#include <memory>

template<typename SharedPointer> struct Holder {

    SharedPointer p;

    Holder(const SharedPointer &p) : p(p) {}
    Holder(const Holder &other) : p(other.p) {}
    Holder(Holder &&other) : p(std::move(other.p)) {}

    void operator () (...) const {}
};


template<class T>
std::shared_ptr<T> to_std_ptr(const boost::shared_ptr<T> &p)
{
    typedef Holder<std::shared_ptr<T>> H;

    if(H *h = boost::get_deleter<H, T>(p))  // get_deleter seems to cause the problem
    {
        return h->p;
    }
    else
    {
        return std::shared_ptr<T>(p.get(), Holder<boost::shared_ptr<T>>(p));
    }
}



Here the code I used to test it:

//main.cpp

template<typename T> class Base 
{
public:
    T value;   
    virtual void abstract() = 0;
    virtual ~Base() {}
};

template<typename T> class Derived : public Base<T>
{
public:    
    virtual void abstract() override {}
    virtual ~Derived() {}
};


int main(int argc, const char * argv[])
{
    boost::shared_ptr<Base<int>> ptr{new Derived<int>()};

    // error here
    std::shared_ptr<Base<int>> a = to_std_ptr(ptr);

    // no error here
    std::shared_ptr<Base<int>> b = to_std_ptr(boost::static_pointer_cast<Derived<int>>(ptr));

    return 0;
}



Here's the error message(shortened):

boost/smart_ptr/shared_array.hpp:111:102: error: array of abstract class type 'Base<int>'
    shared_array( shared_array<Y> const & r, typename boost::detail::sp_enable_if_convertible< Y[], T[] >::type = boost::detail::sp_empty() )

main.cpp:64:40: note: in instantiation of template class 'boost::shared_array<Base<int> >' requested here 
    if(H *h = boost::get_deleter<H, T>(p))

main.cpp:86:36: note: in instantiation of function template specialization 'to_std_ptr<Base<int> >'requested here
    std::shared_ptr<Base<int>> i = to_std_ptr(ptr);

main.cpp:23:18: note: unimplemented pure virtual method 'abstract' in 'Base'
    virtual void abstract() = 0;

What I get from the error message is that the compiler tried to create an array of abstract classes what of course doesn't work. But why is he even trying to do so and what has boost/sharred_array to do with that. Is he maybe picking the wrong overload for boost::get_deleter?

Était-ce utile?

La solution

Here's a small example of where the error comes from:

struct abstract
{
    virtual void foo() = 0;
};

template<class X, class Y>
struct templ {};

template<class T>
struct bar
{
    template<class U>
    bar(templ<U[], T[]>) {} // (A)
};

int main()
{
    bar<abstract> x;
}

It seems even to be illegal to form the type array of [abstract-type] in (A), therefore instantiating the class template bar with the argument abstract makes the program ill-formed.


The same thing is happening in the background for shared_array. But why is shared_array<Base> instantiated?

The free function boost::get_deleter is overloaded, shared_array.hpp adds an overload to the overload set (actually, adds a template):

template< class D, class T > D * get_deleter( shared_array<T> const & p );

Before overload resolution, even before finding out which functions are viable, function templates need to be instantiated. Instantiating this get_deleter template above leads to instantiating shared_array<Base>, which leads to the program being ill-formed.

The solution is, not to let the above get instantiated: Don't supply the template parameter T, it can't deduce the T in shared_array<T> from a shared_ptr<T>: the two types are unrelated.

Change

if(H *h = boost::get_deleter<H, T>(p))

to

if(H *h = boost::get_deleter<H>(p))

and it works.


Explanation why letting T be deduced works:

When writing a function call where a function template could be meant (looking at the name called), the template parameters have to be set. You can supply them explicitly (inside <> as in get_deleter<H, T>). If you don't supply all of them explicitly (as in get_deleter<H>), the remaining ones have to be deduced from the arguments of the function call.

After all template parameters have been either set explicitly or deduced, their occurrences in the function template are substituted. The error when using get_deleter<H, Derived> occurs in this step: the substituted get_deleter looks like this:

template<> H * get_deleter( shared_array<Derived> const & p );

Here, shared_array<Derived> needs to be instantiated. But during this instantiation, the error explained above occurs. (Note: it's not in the immediate context of get_deleter, therefore SFINAE doesn't apply.)

Now, when you don't explicitly supply the second template parameter, it has to be deduced. And this deduction fails for the function template

template< class D, class T > D * get_deleter( shared_array<T> const & p );

if you use an argument expression of type shared_ptr<Derived>. As deduction fails, no instantiation takes place of the function template, and therefore no instantiation of shared_array<Derived> (deduction fails = some template parameters could not be set).

Why does deduction fail? The compiler needs to deduce the template parameter T inside the function parameter type shared_array<T> const& from the argument expression, which is of the type shared_ptr<Derived>. But this deduction can only succeed (with few exceptions) when the function parameter type can be made equal to the argument expression type. I.e., it can only succeed if there's some type X, such that shared_array<X> is the same type as shared_ptr<Derived>. But there isn't: the specializations of shared_ptr and shared_array are unrelated.

Therefore, the template parameter T of this get_deleter overload cannot be deduced; therefore this function template isn't instantiated, and shared_array<Derived> isn't instantiated.

Deduction failure is a special kind of failure (like SFINAE): It doesn't lead to the program being ill-formed (i.e. it doesn't lead to a compilation error). Rather, the function template for which deduction didn't succeed simply doesn't add a function to the overload set. If there are other functions in the overload set, one of those can be called instead.

Another function template

template<class D, class T> D * get_deleter( shared_ptr<T> const & p )

from boost/smart_ptr/shared_ptr.hpp runs through the same process. However, deduction succeeds here, and a specialization get_deleter<H, T> (with the H and T from to_std_ptr) is added to the overload set. It'll later be chosen by overload resolution.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top