Blag

He's not dead, he's resting

C++ Overload Resolution Hate

Sometimes, C++’s overload resolution rules are a pain in the ass.

Let’s say we have the following:

#include <tr1/memory>

struct Bar
{
    Bar()
    {
    }
};

struct Baz
{
    Baz()
    {
    }
};

struct Foo
{
    explicit Foo(const std::tr1::shared_ptr<const Bar> &)
    {
    }

    explicit Foo(const std::tr1::shared_ptr<const Baz> &)
    {
    }
};

Then the following is ambiguous, and won’t compile:

Foo foo(std::tr1::shared_ptr<Bar>(new Bar));

Here’s why: Neither constructor exactly matches the argument given, so the compiler falls back to construction and type conversions. std::tr1::shared_ptr<T_> has an implicit constructor template <typename U_> shared_ptr(const shared_ptr<U_> &), which is good because it lets you use a shared pointer to a derived class when a shared pointer to a base class is expected. But that conversion can take place for all U_, which means the compiler doesn’t know whether you want to convert to a shared pointer to const Bar or const Baz — it isn’t until the constructor body is instantiated that the compiler finds that only one of the two conversions will compile successfully.

So, one has to be explicit when creating the shared pointer:

Foo foo(std::tr1::shared_ptr<const Bar>(new Bar));

Except, usually we create shared pointers using a helper function, to avoid specifying the type name twice:

template <typename T_>
std::tr1::shared_ptr<T_> make_shared_ptr(T_ * const t)
{
    return std::tr1::shared_ptr<T_>(t);
}

So we’re stuck having to use a slightly weird looking allocation:

Foo foo(make_shared_ptr(new const Bar));

Incidentally, C++0x has a std::make_shared which is a lot better than this, but it requires rvalue references and std::forward to work. It would look like this:

Foo foo(std::make_shared<Bar>());

Or, if we still need the const:

Foo foo(std::make_shared<const Bar>());

Why might we not need the const? The current C++0x draft standard includes the wording “[the template] constructor shall not participate in the overload resolution unless U_ * is implicitly convertible to T_ *“, which presumably means implementations have to solve the problem using concepts to restrict the template constructor.

And, of course, there’s one final gotcha. The new const Foo form is only legal if Foo has a user defined constructor. I have no idea why, but C++03 explicitly says so.

Advertisements

One response to “C++ Overload Resolution Hate

  1. pizer March 13, 2009 at 8:57 am

    Actually, it’s possible to solve this with a bit of meta-programming and SFINAE:

    template<typename T>
    class shared_ptr {

    /// new conversion constructor
    template<typename U>
    shared_ptr(shared_ptr<U> const& p,
    typename enable_if<
    is_ptr_convertible<T,U>::value,
    char
    >::type =0)
    {…}


    };

    – P

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s