C++0x Error: overloading a function with std::shar

2019-02-22 04:54发布

问题:

Suppose I have two unrelated classes A and B. I also have a class Bla that uses boost::shared_ptr like this:

class Bla {
public:
    void foo(boost::shared_ptr<const A>);
    void foo(boost::shared_ptr<const B>);
}

Notice the const. That's the important part which the original version of this question lacked. This compiles, and the following code works:

Bla bla;
boost::shared_ptr<A> a;
bla.foo(a);

However, if I switch from using boost::shared_ptr to using std::shared_ptr in the above examples, I get a compilation error that says:

"error: call of overloaded 'foo(std::shared_ptr<A>)' is ambiguous
note: candidates are: void foo(std::shared_ptr<const A>)
                      void foo(std::shared_ptr<const B>)

Can you help me figure out why the compiler can't figure out which function to use in the std::shared_ptr case, and can in the boost::shared_ptr case? I'm using the default GCC and Boost versions from the Ubuntu 11.04 package repository which are currently GCC 4.5.2 and Boost 1.42.0.

Here is the full code that you can try compiling:

#include <boost/shared_ptr.hpp>
using boost::shared_ptr;
// #include <memory>
// using std::shared_ptr;

class A {};
class B {};

class Bla {
public:
    void foo(shared_ptr<const A>) {}
    void foo(shared_ptr<const B>) {}
};

int main() {
    Bla bla;
    shared_ptr<A> a;

    bla.foo(a);

    return 0;
}

By the way, this issue motivated me to ask this question about whether I should be using std::shared_ptr at all yet ;-)

回答1:

shared_ptr has a template single-argument constructor, which is considered for the conversion here. That's what allows an actual parameter shared_ptr<Derived> to be supplied where a shared_ptr<Base> is needed.

Since both shared_ptr<const A> and shared_ptr<const B> have this implicit conversion, it's ambiguous.

At least in C++0x, the standard requires that shared_ptr use some SFINAE tricks to make sure that the template constructor only matches types that actually can be converted.

The signature is (see section [util.smartptr.shared.const]):

shared_ptr<T>::shared_ptr(const shared_ptr<T>& r) noexcept;
template<class Y> shared_ptr<T>::shared_ptr(const shared_ptr<Y>& r) noexcept;

Requires: The second constructor shall not participate in the overload resolution unless Y* is implicitly convertible to T*.

Possibly the library hasn't yet been updated to comply with that requirement. You might try a newer version of libc++.

Boost won't work, because it's missing that requirement.

Here's a simpler test case: http://ideone.com/v4boA (This test case will fail on a conforming compiler, if it compiles successfully, it means the original case will be incorrectly reported as ambiguous.)

VC++ 2010 gets it right (for std::shared_ptr).



回答2:

The following compiles fine with GCC 4.5 and Visual Studio 10. If you say it doesn't compile in GCC 4.5.2 then it sounds like a compiler bug which you should report (but make sure that it really happens it's more likely that you made some sort of typo).

#include <memory>
class A{};
class B{};
class Bla {
public:
    void foo(std::shared_ptr<A>) {}
    void foo(std::shared_ptr<B>) {}
};

int main()
{
    Bla bla;
    std::shared_ptr<A> a;
    bla.foo(a);
}


回答3:

You can use std::static_pointer_cast to add the const qualification:

bla.foo(std::static_pointer_cast<const A>(a));


回答4:

http://bytes.com/topic/c/answers/832994-shared_ptr-derived-classes-ambiguitity-overloaded-functions

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

template < typename From, typename To >
class is_convertible
{
    private:
        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

An in boost's shared_ptr.h, change the constructor signature to:

template<class Y>
shared_ptr(shared_ptr<Y> const & r,
    typename enable_if<is_convertible<Y*, T*>::value, void*>::type = 0
    ): px(r.px), pn(r.pn) // never throws
{
}