6

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?

Mordachai
  • 9,412
  • 6
  • 60
  • 112

2 Answers2

11

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

GManNickG
  • 494,350
  • 52
  • 494
  • 543
  • Hm, the details are interesting, though. You cannot convert `wchar_t*` to `ITEM*`, so wouldn't the templated constructor check for free functions with *convertible* argument types? – Kerrek SB Nov 16 '11 at 18:41
  • I guess it's intuitive to me that a std::function that explicitly specifies the argument and return types would have to apply such restrictions sooner, and counter-intuitive that this is left until later. Bummer. – Mordachai Nov 16 '11 at 18:44
  • 1
    @KerrekSB: AFAIK it's not possible in general. You can't perform any `enable_if`'s, for example, because that would only happen *after* `T` is deduced. The best you could do is add constructor overloads for an exact free function match, but that leaves all the other function-like value ambiguous. – GManNickG Nov 16 '11 at 18:45
  • @GMan: Thanks! It can get a bit hairy at times to think about the order in which arguments are deduced :-) – Kerrek SB Nov 16 '11 at 18:47
  • I know its kind of long shot, but there might be something in boost that enables a function only if it supports call operator with specified signature (to be used with enable_if) – Daniel Nov 16 '11 at 18:52
  • @GMan the cast is slightly off - should be: GetInfo(item, static_cast(GetLength)); – Mordachai Nov 16 '11 at 18:54
  • @Dani: See my comment to Kerrek: there's no `T` with which to test. – GManNickG Nov 16 '11 at 18:59
  • @GMan: There is, because `std::function` has a template parameter for the class and a template parameter for the constructor, which is two to compair. – Daniel Nov 16 '11 at 19:06
  • @GMan so if I'd used a function-pointer definition, then it wouldn't have been ambiguous, but it would have eliminated non-function-ptrs such as functors and lambdas? – Mordachai Nov 16 '11 at 19:06
  • 1
    @Dani: "and a template parameter for the constructor" No it doesn't, that's the problem. Where did it deduce a single `T` from, with an overloaded set of functions? There are multiple types there. You can't deduce `T` until *after* you select a single overload. – GManNickG Nov 16 '11 at 19:24
  • I've added my own solution idea, for those wondering how this problem might be solved. – GManNickG Nov 16 '11 at 19:45
  • @GMan: It can try every one and reject ones that fail, just like SFINAE does – Daniel Nov 16 '11 at 20:11
  • @Dani: You're hand-waving. I'm not sure how else to explain this without going in circles. To use `enable_if`, we need a single type. To get a single type from a set of candidate functions, you have to eliminate all of them but one. Because there are no restrictions on `T` (it can be deduced to be anything), no single best candidate function exists, so a single type for `T` is not deduced, and an error results. At no point do we have a single `T`, so at no point does `enable_if` ever come into play. Let me repeat that: you need a `T` **first**, then `enable_if` can be applied. – GManNickG Nov 16 '11 at 20:27
  • @GMan: No, what I mean is the compiler will try each overload with the function and see what fits. then for each overload there is only one `T` to try with `enable_if` – Daniel Nov 16 '11 at 20:31
  • @Dani: No, actually, it won't; do you have any justification for your claim at all? That's just simply wrong, that's not how template deduction works. Try it yourself and stop hand-waving. – GManNickG Nov 16 '11 at 20:39
0

Your braces dont match. That's why the compiler cant understand you

CString GetInfo(ITEM * item, std::function<double ITEM*> fn)
Adrian Cornish
  • 23,227
  • 13
  • 61
  • 77