4

In the following example, type inference fails for the call to mkPair2:

#include <functional>

template <class A, class B>
struct Pair {
  A left; B right;
};

template <class A, class B>
Pair<A,B> mkPair1 (A left, B right) {
  return (Pair<A,B>) { left, right };
}

template <class A, class B>
std::function<Pair<A,B>(B)> mkPair2 (A left) {
  return [left] (B right) {
    return (Pair<A,B>) { left, right };
  };
}

Pair<int, char> ex1 = mkPair1 (2, 'a');
Pair<int, char> ex2 = mkPair2 (2) ('a');

The problem is that mkPair2 has two template arguments, but the call (2) only provides it with one of them, so the compiler immediately throws its hands up and decides that the program is ambiguous, even though the second type can be inferred from the following ('a') call.

This can be resolved by giving the compiler the types manually mkPair2<int,char> (2) ('a'), but having to hold the compilers hand like that gets old really quickly.

Is there any way to trick the compiler into continuing type checking, provided that every type will eventually be resolved?

BlackCap
  • 1,064
  • 9
  • 14

2 Answers2

7

even though the second type can be inferred from the following ('a') call.

Yes, it could be inferred but the rules of C++ don't allow for that. Type deduction only happens with the function parameters. So if you are lacking a function parameter for the template parameter then you need to specify it yourself.

That said, C++14's auto return type deduction and generic lambdas will save you from having to specify anything. You can rewrite the code as

template <class A, class B>
struct Pair {
  A left; B right;
};

template <class A, class B>
auto mkPair1 (A left, B right) {
  return Pair<A,B>{ left, right };
}

template <class A>
auto mkPair2 (A left) {
  return [left] (auto right) {
    return Pair<A, decltype(right)>{ left, right };
  };
}

Pair<int, char> ex1 = mkPair1 (2, 'a');
Pair<int, char> ex2 = mkPair2 (2) ('a');

and everything will be deduced for you. It also returns the lambda object, instead of std::function so you avoid the cost of the type erasure that std::function uses.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
  • +1, maybe worth mentioning that in c++17 you can let the compiler deduce the template arguments from constructor arguments `Pair ex1 = mkPair1 (2, 'a')` – Andreas DM Feb 06 '19 at 20:28
  • @AndreasDM I was thinking about that but is could be even further simplified to `Pair ex1(2, 'a');`. Unfortunately I know there is a caveat for this case and I need to check my reference on what that is. I might be able to add that in later tonight. – NathanOliver Feb 06 '19 at 20:30
2

In addition to @NathanOliver's answer, you can make it work for C++11 too by creating the equivalent of the generic lambda yourself:

template <class A>
struct X {
    A left;
    X(A left) : left(left) {}

    template <class B>
    Pair<A,B> operator()(B right) {
        return Pair<A,B>{left, right};
    }
};

Then:

template <class A>
X<A> mkPair2(A left) {
    return X<A>(left);
}
Acorn
  • 24,970
  • 5
  • 40
  • 69