9

I'm attempting to make a template function that takes a function as a parameter and the parameter function has arguments for the template to deduce.

Example time:

Here is a function that accepts a fixed function type and works

void func_a(void(*func)(int)) {
    func(1);
}
int main() {
    auto f = [](int x) -> void { printf("%i\n", x); };
    func_a(f);
    return 0;
}

Here is what I want to do, expanding on the first example (this won't compile)

template <typename... T>
void func_b(void(*func)(T...)) {
    func(1);
}
int main() {
    auto f = [](int x) -> void { printf("%i\n", x); };
    func_b(f);       // neither of
    func_b<int>(f); // these work
    return 0;
}

Ideally I'd like func_b to accept both a regular function and a lambda function like func_a does, but with template magics.

5
  • 3
    have you tried using std::function instead of a C-function pointer? Commented Sep 14, 2016 at 9:28
  • @Hayt like replacing void(*func)(T...) with std::function<void(T...)>? If so, yes (and it didn't work for me). Commented Sep 14, 2016 at 9:30
  • 4
    how about just using template <typename Fun> void func_b(Fun&& f)? Your sample makes no sense anyway: you take variadic arguments, but then call the function with one int as argument Commented Sep 14, 2016 at 9:31
  • @stijn I agree my example is a bit contrived, but in my actual program the parameters of the incoming function are very important. Commented Sep 14, 2016 at 9:35
  • What do you mean exactly? The compiler will reject any function if the argument types don't match and there's no implicit conversion. Is it the latter you want to avoid? Commented Sep 14, 2016 at 9:45

2 Answers 2

7

Unfortunately, template deduction doesn't work well with implicit conversions. However, you can convert the lambda explicitly into a function pointer type. The shortest, but somewhat confusing way to do that is to apply unary + operator:

func_b(+f);

Or you could use the more intuitive, but also verbose and DRY-violating cast operation:

func_b(static_cast<void(*)(int)>(f));

But perhaps, you'll want to simply accept any callable type, instead of only function pointers:

template <class Fun>
void func_c(Fun&& func) {
    func(1);
}

This works fine with lambdas. No conversions involved.

func_c(f);

And it also works for capturing lambdas (that cannot be converted to function pointers), std::function and function objects such as those defined in <functional>.

Sign up to request clarification or add additional context in comments.

15 Comments

You probably want a static_cast there -- the chances of an error slipping in are quite high.
@Quentin perhaps so. I implemented your suggestion. It's even more verbose, though.
I really like the + operator idea, but I'm having troubles with the compiler (MSVC 14.0) saying "more that one conversion function from..." when applying the + to the simple lambda in my post.
@MichaelMitchell hmm, works fine in g++. Could be a msvc bug.
Now that I see the + operator it reminds me of how follys scope guard does this. It is a bit complicated but you can checkout github.com/facebook/folly/blob/master/folly/ScopeGuard.h and there are some talks out there by andrei alexandrescu how this works. (this also works on MSVC 14)
|
3

here you have some options that work:

#include <functional>
template <typename T>
void func_b(T&& func) {
    func(1);
}

template <typename... T>
void func_c(std::function<void(T...)> func) {
    func(1);
}    

template <typename... T>
void func_d1(std::function<void(T...)> func) {
    func(1);
}   

template<typename... Params>
using fun = std::function<void(Params...)>;

template <typename T, typename... P>
void func_d(T& func) {
    func_d1(fun<P...>(func));
}

int main() {
    auto f = [](int x) { printf("%i\n", x); };
    func_b(f);
    func_b(std::function<void(int)>(f));

    func_c(std::function<void(int)>(f));

    func_d<decltype(f),int>(f);
    return 0;
}

The issue is: A lambda is not a function pointer or std::function-object.

func_b uses perfect forwarding so T will be the type of the lambda not a std::function object.

For func_c. You should not convert a lambda to a c-style function pointer. A std::function object is able to do this conversion but only explicitly (by design) so you need to explicitly convert them.

func_d (and func_d1) combine the other aspects. It forwards the lambda and makes a std::function-object explicitly out of it though it needs an additional template parameter.

2 Comments

A lambda is a function object. A function object and a std::function are two different (but related) things.
well it satisfies the c++ FunctionObject requirements. But I meant a std::function-object here.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.