Is it possible to catch an exception of lambda type?
While it is good practice to throw only exceptions of types derived from std::exception
class, C++ makes it possible to throw anything. All below examples are valid C++:
throw "foo"; // throws an instance of const char*
throw 5; // throws an instance of int
struct {} anon;
throw anon; // throws an instance of not-named structure
throw []{}; // throws a lambda!
The last example is interesting, as it potentially allows passing some code to execute at catch site without having to define a separate class or function.
But is it at all possible to catch a lambda (or a closure)? catch ([]{} e)
does not work.
Solution 1:
Exception handlers are matched based on type, and the implicit conversions done to match an exception object to a handler are more limited than in other contexts.
Each lambda expression introduces a closure type that is unique to the surrounding scope. So your naive attempt cannot work, for []{}
has an entirely different type in the throw expression and the handler!
But you are correct. C++ allows you to throw any object. So if you explicitly convert the lambda before-hand to a type that matches an exception handler, it will allow you to call that arbitrary callable. For instance:
try {
throw std::function<void()>{ []{} }; // Note the explicit conversion
} catch(std::function<void()> const& f) {
f();
}
This may have interesting utility, but I'd caution against throwing things not derived from std::exception
. A better option would probably be to create a type that derives from std::exception
and can hold a callable.
Solution 2:
C++ allows you to throw anything. And It allows you to catch whatever you throw. You can, of course, throw a lambda. The only problem is that, to catch something, you need to know the type or at least a parent type of that something. Since lambdas do not derive from a common base, you have to know the type of your lambda to catch a lambda. The main issue with that is that every lambda expression will give you an rvalue of a distinct type. That means that both your throw and your catch need to be based on the same lambda expression (note: the same expression, not just some expression that looks exactly the same). One way I can think of to make this work to some degree would be to encapsulate the creation of the lambda to throw in a function. That way, you can call the function in your throw
expression, and use the return type of the function to deduce the type to catch
:
#include <utility>
auto makeMyLambda(int some_arg)
{
return [some_arg](int another_arg){ return some_arg + another_arg; };
}
void f()
{
throw makeMyLambda(42);
}
int main()
{
try
{
f();
}
catch (const decltype(makeMyLambda(std::declval<int>()))& l)
{
return l(23);
}
}
Try it out here.
You could also just use std::function
like suggested in some of the other answers, which is potentially a more practical approach. The downsides of that, however, would be
- It means you don't actually throw a lambda. You throw an
std::function
, which is not really what you asked for 😉 - The creation of an
std::function
object from a lambda can throw an exception
Solution 3:
You can throw and catch a std::function
:
#include <iostream>
#include <functional>
void f() {
throw std::function<void(void)>([]{std::cout << "lambda\n"; });
}
int main()
{
try{ f(); }
catch( std::function<void(void)> &e)
{
e();
std::cout << "catch\n";
}
}
Output:
lambda
catch
Solution 4:
A lambda is a unique anonymous type. The only way to name a lambda instance's type is to store it in a variable, then do a decltype
on that variable type.
There are a few ways you can catch a thrown lambda.
try {
throw []{};
} catch(...) {
}
in this case you cannot use it, other than throwing it again.
try {
throw +[]{};
} catch(void(*f)()) {
}
a stateless lambda can be converted to a function pointer.
try {
throw std::function<void()>([]{});
} catch(std::function<void()> f) {
}
you can convert it to a std::function
. The downside with std::function
is that it heap allocates for larger lambdas, which could in theory cause it to throw.
We can eliminate that heap allocation:
template<class Sig>
struct callable;
template<class R, class...Args>
struct callable<R(Args...)> {
void* state = nullptr;
R(*action)(void*, Args&&...) = nullptr;
R operator()(Args...args) const {
return action( state, std::forward<Args>(args)... );
}
};
template<class Sig, class F>
struct lambda_wrapper;
template<class R, class...Args, class F>
struct lambda_wrapper<R(Args...), F>
:
F,
callable<R(Args...)>
{
lambda_wrapper( F fin ):
F(std::move(fin)),
callable<R(Args...)>{
static_cast<F*>(this),
[](void* self, Args&&...args)->R {
return static_cast<R>( (*static_cast<F*>(self))( std::forward<Args>(args)... ) );
}
}
{}
lambda_wrapper(lambda_wrapper && o):
F(static_cast<F&&>(o)),
callable<R(Args...)>( o )
{
this->state = static_cast<F*>(this);
}
lambda_wrapper& operator=(lambda_wrapper && o)
{
static_cast<F&>(*this) = (static_cast<F&&>(o));
static_cast<callable<R(Args...)>&>(*this) = static_cast<callable<R(Args...)>&>( o );
this->state = static_cast<F*>(this);
}
};
template<class Sig, class F>
lambda_wrapper<Sig, F> wrap_lambda( F fin ) {
return std::move(fin);
}
now you can do:
try {
throw wrap_lambda<void()>([]{});
} catch( callable<void()> const& f ) {
}
callable
is "lighter weight" type erasure than std::function
as it cannot cause new heap memory to be allocated.
Live example.