Reputation: 8930
Consider the following example:
template <typename lambda> void call_me_back(const lambda & callback)
{
// Very complicated calculation to find the meaning of everything
callback(42);
}
int main()
{
call_me_back([](const int & value)
{
std :: cout << value << std :: endl;
});
}
Here I provide call_me_back
with a lambda that accepts an int
. call_me_back
calls callback
after some long calculation, and callback
prints it out. Easy as cake.
Now, say that depending on the execution, call_me_back
needs to call callback
either with an int
or with some other type. We can edit the previous example to
template <typename lambda> void call_me_back(const lambda & callback)
{
// Very complicated calculation to find the meaning of everything
if(rand() % 2)
callback(42);
else
callback("Kill all the humans.");
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
}
So far so good. Now callback
can do all sorts of tricks and handle value
depending on its type.
Now, back to the first example. Say that call_me_back
is not ready to immediately call callback
with an int
. What it can do, however, is to store the callback
somewhere, and then call it later.
For example:
std :: function <void(const int &)> the_callback;
template <typename lambda> void call_me_back(const lambda & callback)
{
the_callback = callback;
}
void ready_to_answer()
{
the_callback(42);
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
}
Now, instead of immediately calling callback
, call_me_back
stores callback
in an std :: function <void(const int &)>
object (I know, it's in the global scope, bear with me). Many things can then happen and at some point someone can call ready_to_answer
, which retrieves the previously stored callback and calls it. For example, ready_to_answer
can be called by another thread, but there is a whole lot of reasons why one would need a paradigm like this, where the callback can be stored and called later.
What if I wanted to implement the second example, but with a delayed callback?. I don't seem to be able to get my head around this.
I can imagine that std :: function
is implemented with a virtual call operator that accepts some specific type. The std :: function
then wraps a pointer / reference to a template wrapper class that stores the actual lambda and implements the call operator by forwarding its argument to the lambda it's storing. Nice and easy. But I can't have template virtual methods!
I have tried coming up with all sorts of solutions, but I couldn't find anything that could reasonably work. Is this really impossible to do? Is it impossible to have some externally provided lambda that accepts a const auto &
argument stored somewhere, and then call it later?
Upvotes: 2
Views: 88
Reputation: 21131
You could implement a class template that stores the lambda for you.
#include<iostream>
template<typename L>
struct Telephone
{
L l;
Telephone(L l) : l{std::move(l)} {}
template<typename... Args>
decltype(auto) ready_to_answer(Args&&... args)
{
return l(std::forward<Args>(args)...);
}
};
template<typename L>
auto call_me_back(L&& l)
{
return Telephone<std::decay_t<L>>(std::forward<L>(l));
}
int main()
{
auto telephone = call_me_back([](auto x){ std::cout << x << std::endl; });
telephone.ready_to_answer(42);
telephone.ready_to_answer("Kill all the humans.");
}
This has the downside that Telephone
is templated on a lambda and will be a different type for each lambda.
If you know beforehand what the function signatures looks like, you could have Telephone
inherit from a common base class and have virtual non-template methods.
Slap on some automatic construction and management of Telephone
and you basically implemented another std::function
but with custom signatures
struct Phone
{
// all Telephone<L> inherits from Phone and have these methods
virtual void ready_to_answer(int) = 0;
virtual void ready_to_answer(const char*) = 0;
};
struct Spouse
{
std::unique_ptr<Phone> phone;
template<typename L>
Spouse(L&& l) : phone{ new Telephone<std::decay_t<L>>{std::forward<L>(l)} } {}
void ready_to_answer(int i) { phone->ready_to_answer(i); }
void ready_to_answer(const char* str) { phone->ready_to_answer(str); }
};
EDIT: I went ahead and implemented a buffed up version of std::function
which accepts any number of signatures, so you could write
function<void (int), void (std::string)> callback =
[](auto x){std::cout << x << std::endl;};
callback(42);
callback("Kill all humans!");
Suffice to say the implementation is not trivial and there is just a bit too much to explain here.
Upvotes: 1
Reputation: 13486
You are correct, it is impossible for an unbounded set of types, but you can do it if you know all the types in advance:
std :: function <void(const std :: variant<int, std :: string> &)> the_callback;
template <typename lambda> void call_me_back(const lambda & callback)
{
the_callback = [callback](const auto & arg)
{
std :: visit(callback, arg);
};
}
template <typename T> void ready_to_answer(const T & x)
{
the_callback(x);
}
int main()
{
call_me_back([](const auto & value)
{
std :: cout << value << std :: endl;
});
if (std :: rand() % 2)
{
ready_to_answer(42);
}
else
{
ready_to_answer("Hi!");
}
}
Upvotes: 7
Reputation: 37478
In your second example you don't pass a function, you pass a reference to anonymous lambda object with two methods operator()(int)
and operator(char const (&)[..])
. So to store this kind of callback you need to either copy lambda object or store particular callback methods, that is using more than one ::std::function
with corresponding signatures. Actually explicitly passing two callbacks for both cases would be clearer.
std::function< void (int) > delalyed_int_cb;
std::function< void (const char *) > delalyed_str_cb;
template< typename callable_taking_int, typename callable_taking_string > void
call_me_back(callable_taking_int && int_cb, callable_taking_string && str_cb)
{
delalyed_int_cb = std::forward< callable_taking_int >(int_cb);
delalyed_str_cb = std::forward< callable_taking_str >(str_cb);
...
}
void
ready_to_answer()
{
if(rand() % 2)
{
delalyed_int_cb(42);
}
else
{
delalyed_str_cb("Kill all the humans.");
}
}
Upvotes: 1