wich
wich

Reputation: 17117

No matching function call for lambda function parameter to template function

Trying to pass a lambda function to a template factory function which is templated on the function arguments of the passed function leads gcc-10.2.0 to report no matching function for call to ‘test(main()::<lambda(int, double)>)’.

It does seem to work when I add a + in front of the lambda function forcing the conversion to a function pointer, but I don't see why that would be necessary. Why does the conversion not happen automatically? Is there any way to make this work?

I have also tried std::function<void(TArgs...)> test_func as argument in the declaration of make_test, however that gives me the same no matching function for call error.

#include <iostream>

template <typename... TArgs>
struct test_type {
  test_type(void(TArgs...)) {};
};

template <typename... TArgs>
test_type<TArgs...> make_test(void test_func(TArgs...)) {
  return test_type<TArgs...>{test_func};
}

int main() {
  auto test_object = make_test([](int a, double b) { std::cout << a << b << "\n"; });

  return 0;
}

Edit

I was wondering if there maybe is some way to make it work with type traits. Something like the following. Although I know of no way to get the argument list from the template parameter.


template <typename F>
test_type<get_arg_list<F>> make_test(std::function<F>&& f) {
  return test_type<get_arg_list<F>>{std::forward(f)};
}

Upvotes: 2

Views: 482

Answers (1)

AndyG
AndyG

Reputation: 41092

In order to support a variety of callables being passed to your factory (e.g., a stateful lambda or a function pointer), your test_type constructor should accept some kind of type-erased function type like std::function<void(int, double)>:

template<class... TArgs>
struct test_type {
  test_type(std::function<void(TArgs...)>) {};
};

Afterwards it's just a matter of writing the boilerplate to handle the following callables being passed to make_test

  • a regular function pointer
  • a lambda (struct with a operator()(...) const)
  • a mutable lambda or a user defined callable without a const operator() function

Here is one approach using type traits:

Start with a base class that we'll specialize for each scenario:

template<class T, class = void>
struct infer_test_type;

(This is a common setup for the voider pattern. We can do this with concepts and constraints, but I'm feeling too lazy to look up the syntax, maybe later)

Regular function pointer specialization

template<class Ret, class... Args>
struct infer_test_type<Ret(*)(Args...)>
{
    using type = test_type<Args...>;
};

Now we can write a templated alias for simplicity:

template<class T>
using infer_test_type_t = typename infer_test_type<T>::type;

And we can verify that it works like so:

void foo(int, double){}
// ... 
static_assert(std::is_same_v<infer_test_type_t<decltype(&foo)>, test_type<int, double>>);

We can use the type trait for our make_test function like so:

template<class T>
auto make_test(T&& callable) -> infer_test_type_t<T>
{
    return infer_test_type_t<T>{std::forward<T>(callable)};
}

Now it's just a matter of covering our other two scenarios.

Callable objects

  • these will have operator() (either const or not)

I'll start with a top level trait to detect the presence of operator() and feed the type of operator() into another trait.

The top level trait:

// if T is a callable object
template<class T>
struct infer_test_type<T, std::void_t<decltype(&T::operator())>>
{
    using type = typename infer_test_type<decltype(&T::operator())>::type;
};

You see that internally it's calling back into another infer_test_type specialization that I haven't shown yet; one that is specialized for operator(). I'll show the two specializations now:

// if operator() is a const member function
template<class T, class Ret, class... Args>
struct infer_test_type<Ret(T::*)(Args...) const>
{
    using type = test_type<Args...>;
};

// if operator() is non-const member function
template<class T, class Ret, class... Args>
struct infer_test_type<Ret(T::*)(Args...)>
{
    using type = test_type<Args...>;
};

(We could probably combine these two if we wanted to be a little bit smarter and lop off any const at the high level before calling down, but I think this is more clear)

And now we should be able to infer an appropriate test_type for non-generic callables (no generic lambdas or templated operator() functions):

a test with a non-const operator():

struct my_callable
{
    void operator()(int, double) // non-const
    {
    }
};

// ...
static_assert(std::is_same_v<infer_test_type_t<my_callable>, test_type<int, double>>);

And a test with your lambda:

auto lambda = [](int a, double b) { std::cout << a << b << "\n"; };
static_assert(std::is_same_v<infer_test_type_t<decltype(lambda)>, test_type<int, double>>);

Putting it all together

For your simple (non-capturing, non-generic lambda) example it's quite straightforward:

make_test([](int a, double b) { std::cout << a << b << "\n"; });

Demo

Upvotes: 1

Related Questions