c++templateslambdaambiguity

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.


Solution

  • The chapter 5.1.2/3 ([expr.prim.lambda]) of C++ standard says:

    The type of the lambda-expression (which is also the type of the closure object) is a unique, unnamed nonunion class type

    So the two lambdas in this line

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

    have different types, even though they look the same. If you want WhichOp2 to work like this you have to declare it with different operation types:

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