Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Function overloading with shared pointer argument ambiguity

I want to make overloaded functions that take a shared pointer to a base class and derived classes. It seems to work for references and raw pointers, but not for shared pointers in the case of an extra derived class. See the example code:

#include <memory>

class Base{};
class Derived : public Base {};
class ExtraDerived : public Derived {};


bool IsBase(Base*){ return true; }
bool IsBase(Derived*){ return false; }

bool IsBase(std::shared_ptr<Base>){ return true; }
bool IsBase(std::shared_ptr<Derived>){ return false; }

int main() 
{
    auto derived = std::make_shared<Derived>();
    auto extra_derived = std::make_shared<ExtraDerived>();
    // works
    auto raw_result_derived = IsBase(derived.get());
    auto raw_result_extra_derived = IsBase(extra_derived.get());
    auto shared_result_derived = IsBase(derived);
    // doesn't work
    auto shared_result_extra_derived = IsBase(extra_derived);
}

I get: "error C2668: 'IsBase' : ambiguous call to overloaded function" when using Visual Studio 2012, but I also get the same result when I try the code on here http://ideone.com/6uoa0p.

This doesn't seem like desired behaviour (as it works for 'raw' stuff). Is this a limitation of the templating, is there another reason why this doesn't work or is it a bug? And how can I make it work in the least ugly way?

The best I can come up with is

//ugly workaround
bool IsBase(std::shared_ptr<Base>, Base*){ return true; }
bool IsBase(std::shared_ptr<Derived>, Derived*){ return false; }
template<typename T> bool IsBase(std::shared_ptr<T> input )
{
    return IsBase(input, input.get());
}
like image 894
Bas Avatar asked Mar 28 '13 20:03

Bas


1 Answers

Is this a limitation of the templating, is there another reason why this doesn't work or is it a bug?

No, it is not a bug. Indeed it seems you've hit the only pitfall of smart pointers: an std::shared_ptr<base> can be constructed from std::shared_ptr<derived> as well as from std::shared_ptr<extra_derived>, but none of these two conversion sequences is better than the other (being two user-defined conversion sequences of the same length).

However, you can still fix your overloads by using some SFINAE constraints:

#include <type_traits>

// Selected for `std::shared_ptr<Base>`
template<typename T, typename std::enable_if<
    std::is_same<T, Base>::value>::type* = nullptr>
bool IsBase(std::shared_ptr<T>){ return true; }

// Selected for `std::shared_ptr<T>` where T is a class derived from Base,
// but not Base itself
template<typename T, typename std::enable_if<
    std::is_base_of<Base, T>::value &&
    !std::is_same<T, Base>::value
    >::type* = nullptr>
bool IsBase(std::shared_ptr<T>){ return false; }
like image 59
Andy Prowl Avatar answered Nov 19 '22 03:11

Andy Prowl