Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Two lambdas passed to template function makes type deduction of parameter ambiguous -- why?

I have a template that works if I pass it one lambda, but in a related template that takes two lambdas mapped to the same templated type, it cannot deduce that type, and MSVC++ Express 2013 complains the template parameter is ambiguous. To be clear up front, there is no overloading (or specialization) going on here -- my two examples below are the only entities with those identifiers. Here are the templates, which simply apply the callable objects on an argument and return a result:

    template <class A, class OP>
    auto WhichOp1(A argument, OP firstOp)->decltype(firstOp(argument)) {
        return firstOp(argument);
    }

    template <class A, class OP>
    auto WhichOp2(A argument, OP firstOp, OP secondOp)->decltype(firstOp(argument)) {
        return firstOp(argument) + secondOp(argument);
    }

I can use WhichOp1 successfully like so:

    int e = WhichOp1(2, [](int i){return i * 2; });

But a similar call to WhichOp2 won't compile:

    int d = WhichOp2(2, [](int i){return i * 2; }, [](int i){return i * 3; });

I get the following errors:

error C2782: 'unknown-type chaj::ops::WhichOp2(A,OP,OP)' : template parameter 'OP' is ambiguous

IntelliSense: no instance of function template "chaj::ops::WhichOp2" matches the argument list argument types are: (int, lambda []int (int i)->int, lambda []int (int i)->int)

What I gather is that it simply can't take the first lambda with the second one and determine between the two what exactly OP's type should be. If I explicitly instantiate, it works fine to resolve the ambiguity:

    int b = WhichOp2<int, int(*)(int)>(2, [](int i){return i * 2; }, [](int i){return i * 3; });

So my question is simply an attempt to have a better understanding of what is going on -- why can the compiler not resolve ambiguity when passing two similar lambdas to a template under a common template parameter? The intellisense error seems to map the type of the lambdas to the same type. I won't be surprised if this is compiler specific, but if anyone sees that this works on their compiler, I'd be interested to know.

like image 687
Charles J. Daniels Avatar asked Dec 26 '22 03:12

Charles J. Daniels


1 Answers

Each lambda defines a unique type. Even if they take the same parameter(s) and return the same type, two separate lambdas are still two separate types.

Since they're separate types, to the compiler it's roughly as if you had attempted to do something like:

template <class T>
T foo(T a, T b) { return a + b; }

...and then tried to do something like: auto x = foo(1, 2.0); Since you've passed an int and a double, the compiler can't decide whether T is int or double.

The fix is the same in both cases: specify a separate template parameter for each if you might be using lambdas.

Note that this is not unique to lambdas either. The same can happen if you use explicitly defined function objects. For example:

struct foo { 
    bool operator()();
};

struct bar {
    bool operator()();
};

template <class T>
void baz(T a, T b) { /* ... */ }

baz(foo(), bar());

Here, since you've defined foo and bar yourself, it's pretty obvious that they're two separate types, even though they both define an operator() of the same type. Lambda expressions do exactly the same, aside from minor (and irrelevant) details about the names of the classes defined by the lambda expression.

like image 120
Jerry Coffin Avatar answered Feb 22 '23 23:02

Jerry Coffin