Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++0x Error: overloading a function with std::shared_ptr to const argument is ambiguous

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 ;-)

like image 953
Alan Turing Avatar asked Jun 12 '11 14:06

Alan Turing


3 Answers

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).

like image 156
Ben Voigt Avatar answered Nov 15 '22 05:11

Ben Voigt


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);
}
like image 27
Motti Avatar answered Nov 15 '22 06:11

Motti


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

bla.foo(std::static_pointer_cast<const A>(a));
like image 40
Jon Purdy Avatar answered Nov 15 '22 05:11

Jon Purdy