59

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.

Update (2022/11/14):

See here my own answer taking into account features of C++20.

Krzysiek Karbowiak
  • 1,655
  • 1
  • 9
  • 17

5 Answers5

52

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • 8
    Sure, I **would not** use this in production code. Rather, I was exploring intricacies of the language. And while I did try to catch by pointer-to-function, it did not occur to me to throw and catch as `std::function`. – Krzysiek Karbowiak Dec 05 '18 at 10:25
  • 4
    @KrzysiekKarbowiak - If the type is designed carefully I don't see why you can't do it in production. It may have interesting utility, like I noted. Ingenuity is after all taking what is known already and using it in a novel way :) – StoryTeller - Unslander Monica Dec 05 '18 at 10:27
  • 2
    The exact rules used to match `catch` expressions are available in [this answer](https://stackoverflow.com/questions/2343208/can-you-catch-an-exception-by-the-type-of-a-conversion-operator). Sadly, it looks like there's no way to catch the lambda if it's thrown as `throw []{};`, as there's no public base class and the type is not a pointer so the pointer rules don't apply. – Silvio Mayolo Dec 05 '18 at 19:07
25

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
Michael Kenzel
  • 15,508
  • 2
  • 30
  • 39
8

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
perivesta
  • 3,417
  • 1
  • 10
  • 25
2

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.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
0

It is now 2022, the current C++ standard is C++20, and it gives us some more interesting opportunities, which I realised after seeing this talk: C++ Lambda Idioms, especially this part.

Lambdas are now default-constructible and allowed in unevaluated contexts (e.g. decltype), which allows us to do this:

#include <iostream>

using lambda = decltype([]{ std::cout << "I am a lambda\n"; });

auto foo()
{
    throw lambda();
}

int main()
{
    try {
        foo();
    } catch (const lambda& l) {
        l();
    }
}

Try it out on Compiler Explorer.

So it looks like currently the short answer is Yes, it is possible to catch an exception of lambda type.

Krzysiek Karbowiak
  • 1,655
  • 1
  • 9
  • 17