Is it possible to catch an exception of lambda type?

C++ExceptionLambda

C++ Problem Overview


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.

C++ Solutions


Solution 1 - C++

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++

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 - C++

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 - C++

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.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKrzysiek KarbowiakView Question on Stackoverflow
Solution 1 - C++StoryTeller - Unslander MonicaView Answer on Stackoverflow
Solution 2 - C++Michael KenzelView Answer on Stackoverflow
Solution 3 - C++perivestaView Answer on Stackoverflow
Solution 4 - C++Yakk - Adam NevraumontView Answer on Stackoverflow