Lambda is deduced to std::function if template has no variadic arguments
Asked Answered
A

2

12
template<typename ReturnT, typename... ParamT>
void foo(std::function<ReturnT(ParamT...)> callback)
{}

template<typename ReturnT, typename ParamT>
void bar(std::function<ReturnT(ParamT)> callback)
{}

main()
{    
    foo<int, int>([](int x){ return x; });  // no instance of function 
                                            //   template matches argument list
    bar<int, int>([](int x){ return x; });  // OK
}

The only difference between foo and bar is that foo has variadic arguments. Somehow the compiler is able to convert the lambda to a std::function in bar.

To my understanding, template type deduction doesn't consider type conversions. So shouldn't both fail?

Accuracy answered 17/10, 2018 at 8:19 Comment(1)
Depending on use-case and your needs, I suggest you don't use std::function as argument, and instead look at how the standard library handles callable objects using only a single template argument.Forepart
G
7
template<typename ReturnT, typename... ParamT>
void foo(std::function<ReturnT(ParamT...)> callback)
{}

now, foo<int,int> is foo<ReturnT=int, ParamsT starts with {int}>.

It does not fully specify ParamT. In fact, there is no way to fully specify ParamT.

As an incompletely specified template, deduction occurs, and fails. It doesn't try "what if I just assume the pack doesn't go any further".

You can fix this with:

template<typename ReturnT, typename... ParamT>
void foo(block_deduction<std::function<ReturnT(ParamT...)>> callback)
{}

where block_deduction looks like:

template<class T>
struct block_deduction_helper { using type=T; }:
template<class T>
using block_deduction = typename block_deduction_helper<T>::type;

now deduction is blocked on foo's first argument.

And your code works.

Of course, if you pass in a std::function it will no longer auto-deduce arguments.

Note that deducing the type of a a type erasure type like std::function is usually code smell.

Replace both with:

template<class F>
void bar(F callback)
{}

if you must get arguments, use function traits helpers (there are many on SO). If you just need return value, there are std traits that already work that out.


In you can do this:

tempate<class R, class...Args>
void bar( std::function<R(Args...)> f ) {}
template<class F>
void bar( F f ) {
  std::function std_f = std::move(f);
  bar(std_f);
}

using the deduction guides feature.

Gleeful answered 17/10, 2018 at 13:49 Comment(2)
can you clarify why "deducing the type of a type erasure type like std::function" is usually a code smell?Accuracy
Type erasure and type deduction are rough inverses. Doing both is like taking a int*const* and unconditionally dereferencing it. The & at call site and unconditional * at use site indicate a problem; so is deducing the type to type erase a type to.Gleeful
S
10

You don't have any deduction for the type parameters of bar, they are fully specified.

You still have the tail of the pack to deduce in foo, and that fails because the lambda isn't a std::function.

Stope answered 17/10, 2018 at 8:26 Comment(3)
Presumably some type deduction happens for the call to bar in the argument type conversion when it tries to find the appropriate std::function constructor. Just not directly to find the right bar.Lopsided
@Lopsided not type deduction, but conversion. They are two distinct concepts which happen at different phases of compilation.Salient
@RichardHodges but during type conversion there's some type deduction happening in the std::function constructorLopsided
G
7
template<typename ReturnT, typename... ParamT>
void foo(std::function<ReturnT(ParamT...)> callback)
{}

now, foo<int,int> is foo<ReturnT=int, ParamsT starts with {int}>.

It does not fully specify ParamT. In fact, there is no way to fully specify ParamT.

As an incompletely specified template, deduction occurs, and fails. It doesn't try "what if I just assume the pack doesn't go any further".

You can fix this with:

template<typename ReturnT, typename... ParamT>
void foo(block_deduction<std::function<ReturnT(ParamT...)>> callback)
{}

where block_deduction looks like:

template<class T>
struct block_deduction_helper { using type=T; }:
template<class T>
using block_deduction = typename block_deduction_helper<T>::type;

now deduction is blocked on foo's first argument.

And your code works.

Of course, if you pass in a std::function it will no longer auto-deduce arguments.

Note that deducing the type of a a type erasure type like std::function is usually code smell.

Replace both with:

template<class F>
void bar(F callback)
{}

if you must get arguments, use function traits helpers (there are many on SO). If you just need return value, there are std traits that already work that out.


In you can do this:

tempate<class R, class...Args>
void bar( std::function<R(Args...)> f ) {}
template<class F>
void bar( F f ) {
  std::function std_f = std::move(f);
  bar(std_f);
}

using the deduction guides feature.

Gleeful answered 17/10, 2018 at 13:49 Comment(2)
can you clarify why "deducing the type of a type erasure type like std::function" is usually a code smell?Accuracy
Type erasure and type deduction are rough inverses. Doing both is like taking a int*const* and unconditionally dereferencing it. The & at call site and unconditional * at use site indicate a problem; so is deducing the type to type erase a type to.Gleeful

© 2022 - 2024 — McMap. All rights reserved.