Rook
Rook

Reputation: 6145

Template argument deduction from lambda

I'm attempting to write a function that basically transforms an instance of a class templated on one type to an instance of the same class templated on a second type. I'd like to avoid having to explicitly state the template types when calling the function.

Here's a minimal compilable example of what I'm trying to do:

template<class T> class container {};

template <class A, class B, template <class C> class Container>
Container<B> transform(Container<A> c, B(func)(A))
{
  return Container<B>{};
}

int do_something(int in)
{
  return in + 1;
}

int main()
{
  container<int> c{};

  //this one doesn't work:
  //transform(c, [](int in) -> int { return in + 1; });
  //these are fine:
  transform<int, int>(c, [](int in) -> int { return in + 1; });
  transform(c, do_something);

  return 0;
}

Uncommenting out the first transform call results in compile errors:

Visual Studio 2017:

error C2784: 'Container<B> transform(Container<A>,B (__cdecl *)(A))': could not deduce template argument for 'B (__cdecl *)(A)' from 'test::<lambda_afc081691b59f849887abca17e74b763>'

Whichever version of g++ coliru.stacked-crooked.com uses by default:

main.cpp:4:14: note:   template argument deduction/substitution failed:
main.cpp:18:52: note:   mismatched types 'B (*)(A)' and 'main()::<lambda(int)>'
   transform(c, [](int in) -> int { return in + 1; });
                                                   ^

Does this mean that it is not possible for the compiler to deduce the signature of a lambda, even when it has been clearly defined like this?

I know I can rewrite my transform function like this:

template <class A, template <class C> class Container, class F>
auto transform(Container<A> c, F func)->Container<decltype(func(A{}))>
{
  return Container<decltype(func(A{}))>{};
}

but now the function signature is a little less readable and the error messages I get if I supply an inappropriate function are quite unfriendly. Using a std::function<B(A)> doesn't help either.

Is there a way to use the more tightly specified function argument with lambdas and without explicitly adding the template types?

Upvotes: 4

Views: 1674

Answers (2)

Jarod42
Jarod42

Reputation: 217085

B and A cannot be deduced in B(func)(A) from lambda.

You might change your template to be more generic, something like:

template <template <typename...> class Container, typename Ts...>
auto transform(const Container<Ts...>& c, F f)
-> Container<std::decay_t<decltype(f(*c.begin())>>
{
    return {};
}

Upvotes: 2

You need to convert the capture-less lambda into the static function that preforms the operation. That conversion can actually be invoked fairly easily with an application of the unary + operator.

transform(c, +[](int in) -> int { return in + 1; });

Since the closure type of a capture-less lambda has a conversion operator to ret(*)(params), the compiler will invoke it when encountering +. That is because you can actually apply a + to pointer types.

[expr.unary.op/7]

The operand of the unary + operator shall have arithmetic, unscoped enumeration, or pointer type and the result is the value of the argument. Integral promotion is performed on integral or enumeration operands. The type of the result is the type of the promoted operand.

Upvotes: 4

Related Questions