Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does C++ see this as ambiguous function reference

Why might my compiler see the following GetLength function pointer as ambiguous
pseudo-code:

size_t GetLength(char*);
size_t GetLength(wchar_t*);
struct ITEM { };
double GetLength(ITEM*);

CString GetInfo(ITEM * item, std::function<double (ITEM*)> fn)
{
  ... omitted for clarity
}

ITEM * item = new ITEM;
cout << GetInfo(item, GetLength);  // <- ambiguous error

GetInfo only allows something of the double return + ITEM* argument pattern. So why is it considering (and not discarding) the two string based variations of GetLength?

like image 505
Mordachai Avatar asked Nov 16 '11 18:11

Mordachai


Video Answer


1 Answers

The constructor for std::function<...> is templated because it has to be able to support any function-like input type. There's no single type to try to deduce to, so your overloads are all possible to construct with; it wouldn't be until later into compilation later that an error arose for a type mismatch.

You could do this:

GetInfo(item, static_cast<double(*)(ITEM*)>(GetLength));

To explicitly discard the other overloads.


In other words, it's the same reason this won't work:

void foo(int);
void foo(void*);

struct bar
{
    template <typename T>
    bar(T f)
    {
        f(5);
    }
};

bar b(foo);

Even though the constructor body for bar will only work with void foo(int), it wants to support any function where f(5) will work so the argument type is templated. This allows any function to work in that place, which means the compiler cannot deduce a single best overload to use.


I think that one language-level solution is for an overload set to actually be a functor itself. That is, given:

void foo(int);
void foo(void*);

template <typename T>
double foo(int, T);

Naming foo (as in bar(foo) or even just foo(5)) results in an instance of this type:

struct __foo_overload_set // internal
{
    // forwarders
    void operator()(int __arg0) const
    {
        // where __foo0 is the zeroth overload, etc...
        return __foo0(__arg0);
    }

    void operator()(void* __arg0) const
    {
        return __foo1(__arg0);
    }

    template <typename __Arg1>
    double operator()(int __arg0, __Arg1&& __arg1) const
    {
        return __foo2(__arg0, std::forward<__Arg1>(__arg1));
    }

    // converters
    typedef void(*__foo0_type)(int);

    operator __foo0_type() const
    {
        return __foo0;
    }

    typedef void(*__foo1_type)(void*);

    operator __foo1_type() const
    {
        return __foo1;
    }

    template <typename T>
    struct __foo2_type
    {
        typedef void(*type)(int, T);
    };

    template <typename T>
    operator typename __foo2_type<T>::type() const
    {
        return __foo2;
    }
};

Which, being callable itself, will compile in the context we want. (AFAIK, it does not introduce any ambiguities that don't already exist, though it's completely untested.)

like image 141
GManNickG Avatar answered Sep 22 '22 13:09

GManNickG