4
votes

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.

2

2 Answers

4
votes

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);
}
6
votes

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.