Reputation: 21
I need help with the syntax for the following C++ (17) code:
#include <iostream>
struct st_a {
int var;
};
struct st_b {
double var;
};
void func(void(cb)(st_a)) {
st_a a;
a.var = 3;
cb(a);
}
void func(void(cb)(st_b)) {
st_b b;
b.var = 4;
cb(b);
}
class test {
public:
test() {
func([](st_a arg) { std::cout << arg.var; });
func([this](st_a arg) { my_func_a(arg); });
func([this](st_b arg) { my_func_b(arg); });
}
void my_func_a(st_a arg) {
std::cout << arg.var;
}
void my_func_b(st_b arg) {
std::cout << arg.var;
}
};
int main(void)
{
test t;
}
Unsurprisingly the compiler gives me
error: no matching function for call to ‘func(test::test()::<lambda(st_a)>)’
I attempted to rectify it using template functions, i.e.,
template<typename F>
void func(F cb) {
st_a a;
a.var = 3;
cb(a);
}
template<typename F>
void func(F cb) {
st_b b;
b.var = 4;
cb(b);
}
But this results in
error: redefinition of ‘template<class F> void func(F)’
...which is no surprise.
My question is how can I change the template parameters to specify the function argument?
I forgot to mention that I'm chasing nanoseconds, so std::function
is not an option.
The assembly output from Yksisarvinen's and max66's solutions is identical, branch-free, and very compact (the whole program compiles into 9 assembly instructions). The std::function
assembly on the other hand contains page upon page of branches and function calls, which invalidates this approach from an execution speed prespective.
From a code readability perspective, perhaps std::function
is the best option. In my opinion, the second best alternative is the one max66 presented, i.e
template <typename F> auto func (F cb) -> decltype(cb(std::declval<st_a>()), void()) { ...; }
this is also the best approach for portability (C++11) when compared to Yksisarvinen's solution.
Thank you for your help.
Upvotes: 2
Views: 132
Reputation: 66200
Another way (almost equivalent to the std::is_invocable_v
answer from Yksisarvinen, but working also before C++17) could be the following
template <typename F>
auto func (F cb) -> decltype( cb(std::declval<st_a>()), void() )
{
st_a a{3};
cb(a);
}
template <typename F>
auto func (F cb) -> decltype( cb(std::declval<st_b>()), void() )
{
st_b b{4};
cb(b);
}
Upvotes: 1
Reputation: 22219
The other answers explain the core issue with capturing lambda, but you can solve the problem without std:function
by using some template metaprogramming with std::enable_if
.
template<typename F, std::enable_if_t<std::is_invocable_v<F, st_a>, bool> = true>
void func(F cb) {
st_a a;
a.var = 3;
cb(a);
}
template<typename F, std::enable_if_t<std::is_invocable_v<F, st_b>, bool> = true>
void func(F cb) {
st_b b;
b.var = 4;
cb(b);
}
With definitions as such, compiler will only generate first func
if F
can be invoked with argument of type st_a
and second func
if the argument can be invoked with argument of type st_b
. It will probably break if both could be true at the same time (e.g. if you introduce inheritance between st_a
and st_b
).
Upvotes: 2
Reputation: 2423
The problem is that the lambdas defined here
func([this](st_a arg) { my_func_a(arg); });
func([this](st_b arg) { my_func_b(arg); });
have captures. You can think of a lambda as a class that implements operator()
. Lambdas with captures have members in that class.
If you have a lambda with no captures, the compiler provides an implicit conversion to a function pointer, because this is essentially what it is.
If you have a lambda with captures, the compiler can not provide an implicit conversion because you would not be able to fit all these captures into a single pointer.
(If you are interested in lambdas in C++, I'd recommend looking at Jason Turners "Weekly C++" episodes from which many are about lambdas. This one talks about what lambdas are.)
The C++ standard library provides std::function
which is some type-erasure magic that, honestly, I don't fully understand. (I remember watching a C++ Weekly episode about that, but I can't recall properly what it does.)
Anyways, by taking a std::function
you get a small overhead and the standard library will just make everything work.
Upvotes: 1
Reputation: 1113
You are passing a lambda function to something that expects a function pointer, so the type doesn't match. I would suggest changing func
to accept a std::function
as callback, so that you can pass a lambda to it
#include <functional>
void func(std::function<void(st_a)> cb) {
st_a a;
a.var = 3;
cb(a);
}
void func(std::function<void(st_b)> cb) {
st_b b;
b.var = 4;
cb(b);
}
Upvotes: 0