简体   繁体   中英

Cast lambda to std::function with parameter pack

There are several questions on SO that relate to casting lambdas to std::function s, but I have yet to see one that uses a parameter pack for the argument list. This seems broken on my version of g++ (7.1.1-4), and possibly it's just not supported. So is this legal c++17 (by the standard)? If not, why?

#include <functional>

template <typename TReturn, typename ... TArgs>
void Functor(std::function<TReturn (TArgs...)> f) {}

int main(int argc, char * argv[]) {
    auto x = [] (int a, int b) { return a * b; };
    Functor<int, int, int>(x);
    return 0;
}

The code above won't compile because it fails type deduction. Obviously explicitly typing x as std::function<int (int, int)> instead of using auto makes the error go away. But that doesn't allow me to pass an r-value to Functor as I would like. I would also like to not loose any type-safety by using another template parameter for the function type.

What I really don't understand is why the above code fails to compile, but the below code is fine and works:

#include <functional>

template <typename TReturn, typename TArgA, typename TArgB>
void Functor(std::function<TReturn (TArgA, TArgB)> f) {}

int main(int argc, char * argv[]) {
    auto x = [] (int a, int b) { return  a * b; };
    Functor<int, int, int> (x);
    return 0;
}

The issue is that the compiler doesn't know that you've intended int, int to be the whole of TArgs , and so tries to deduce the remainder of TArgs from the argument f .

For example, this would be valid:

Functor<int, int, int>(std::function<int(int, int, char, float)>{});
// TArgs := {int, int, [...]                       char, float}

So you need to instruct the compiler to not try to deduce the remainder of TArgs . For example, you could write:

(*Functor<int, int, int>)(x);

Or you could write Functor with a non-decomposed signature Sig :

template <Sig>
void Functor(std::function<Sig> f) {}

Or you could wrap the use of TArgs in the parameter f in a non-deduced context:

template <typename TReturn, typename ... TArgs>
void Functor(std::function<std::conditional_t<false, void, TReturn (TArgs...)>> f) {}

This fails:

#include <functional>

template <typename TReturn, typename ... TArgs>
void Functor(std::function<TReturn (TArgs...)> f) {}

int main(int argc, char * argv[]) {
    auto x = [] (int a, int b) { return a * b; };
    Functor<int, int, int>(x);
    return 0;
}

because you're not specifying that the entirety of TArgs... is {int, int} . What you are doing is specifying that the first two types are {int, int} . Effectively, by providing those three types, we've turned the deduction problem into:

template <typename ... TArgs>
void Functor(std::function<int(int, int, TArgs...)> f) {}

int main(int argc, char * argv[]) {
    auto x = [] (int a, int b) { return a * b; };
    Functor(x);
    return 0;
}

This doesn't compile because a lambda isn't a std::function (or derived from one), which is the same reason you couldn't have called this without having provided any types to begin with.

The non-variadic version doesn't have this problem, since you've provided all the types.


But really, what you want is:

template <typename F>
void Functor(F ) {}

This doesn't lose you any type safety. It's using std::function that loses type information, since that class template exists to type erase.

It is very rarely a good idea to cast a lambda to a std::function in a template if you are just going to call it. std::function is type-erasure, and templated type erasure only makes sense if you are going to "pass it on" somewhere else and/or return it.

In any case, try this:

template <class Sig>
void Functor(std::function<Sig> f) {}

int main(int argc, char * argv[]) {
  auto x = [] (int a, int b) { return a * b; };
  Functor<int(int, int)>(x);
  return 0;
}

but you should really just do

template <class F>
void Functor(F f) {}

which is perfectly type-safe.

If you want early type checking, you could write

template<class Sig, class F>
struct signature_compatible;
template<class R, class...Args, class F>
struct signature_compatible<R(Args...), F> :
  std::is_consructible< R, std::result_of_t<F(Args...)>>
{};

then do

template <class Sig, class F>
void Functor(F f) {
  static_assert( signature_compatible<Sig, F&>::value, "bad signature" );
}

but only if you really need to.

Here is a solution that will let you call functor without specifying it's template argument:

#include <functional>
#include <type_traits>

template <class T> struct Fun_trait {};

template <class T, class... Args, class Ret>
struct Fun_trait<auto (T::*) (Args...) const -> Ret>
{
    using F = std::function<auto (Args...) -> Ret>;
};


template <class TReturn, class... TArgs>
void functor(std::function<TReturn (TArgs...)> f) {}

template <class F>
std::void_t<decltype(&F::operator())>
functor(F f)
{
    return functor<typename Fun_trait<decltype(&F::operator())>::F>(f);
};


int main(int argc, char * argv[])
{
    auto x = [] (int a, int b) { return a * b; };

    // nice and easy:
    functor(x); 

    return 0;
}

This is just a lazy first draft to get you started. You need to expand it to support forwarding and non-const operator() .


It works in 2 stages:

1st we have Fun_trait who - for pointer method types (eg the operator() of a lambda) - has defined an alias F for the required std::function argument type.

Next we have a overload of your function functor which via SFINAE with std::void_t kicks in only for functors with a non-overloaded operator() (eg a lambda) and then using the above trait calls the main function functor with the correct template argument deduced.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM