Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Lazy type-inference for curried functions

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?

like image 841
BlackCap Avatar asked Jan 02 '23 08:01

BlackCap


2 Answers

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.

like image 166
NathanOliver Avatar answered Jan 13 '23 07:01

NathanOliver


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);
}
like image 34
Acorn Avatar answered Jan 13 '23 07:01

Acorn