web-dev-qa-db-fra.com

Est-il possible d'attraper une exception de type lambda?

Bien qu'il soit judicieux de ne lancer que des exceptions de types dérivés de la classe std::exception, Le C++ permet de tout jeter. Tous les exemples ci-dessous sont valides en 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!

Le dernier exemple est intéressant, car il permet potentiellement de transmettre du code à exécuter sur le site de capture sans avoir à définir une classe ou une fonction distincte.

Mais est-il possible d'attraper un lambda (ou une fermeture)? catch ([]{} e) ne fonctionne pas.

55
Krzysiek Karbowiak

Les gestionnaires d'exceptions sont mis en correspondance en fonction du type et les conversions implicites effectuées pour associer un objet exception à un gestionnaire sont plus limitées que dans d'autres contextes.

Chaque expression lambda introduit un type de fermeture unique à la portée environnante. Donc, votre tentative naïve ne peut pas fonctionner, car []{} a un type totalement différent dans l’expression de projection et le gestionnaire!

Mais vous avez raison. C++ vous permet de jeter n'importe quel objet. Ainsi, si vous convertissez explicitement l’avant-fichier lambda en un type qui correspond à un gestionnaire d’exceptions, il vous permettra d’appeler cet appelable arbitraire. Par exemple:

try {
    throw std::function<void()>{ []{} }; // Note the explicit conversion
} catch(std::function<void()> const& f) {
    f();
}

Cela peut avoir une utilité intéressante, mais je vous déconseille de jeter des choses qui ne proviennent pas de std::exception. Une meilleure option serait probablement de créer un type dérivé de std::exception et peut tenir un appelable.

50
StoryTeller

C++ vous permet de jeter n'importe quoi. Et cela vous permet d'attraper tout ce que vous lancez. Vous pouvez bien sûr lancer un lambda. Le seul problème est que, pour attraper quelque chose, vous devez connaître le type ou au moins un type parent de ce quelque chose. Puisque les lambdas ne proviennent pas d'une base commune, vous devez connaître le type de votre lambda pour attraper un lambda. Le problème principal avec cela est que chaque expression lambda vous donnera une valeur de type type distinct . Cela signifie que votre lancer et votre capture doivent tous deux être basés sur la même expression lambda (remarque: la même expression, pas seulement une expression qui se ressemble exactement). Une façon dont je peux penser à faire ce travail dans une certaine mesure serait d'encapsuler la création du lambda pour ajouter une fonction. De cette façon, vous pouvez appeler la fonction dans votre expression throw et utiliser le type de retour de la fonction pour en déduire le type à 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);
    }
}

Essayez-le ici .

Vous pouvez aussi simplement utiliser std::function comme suggéré dans certaines des autres réponses, ce qui est potentiellement une approche plus pratique. Les inconvénients de cela, cependant, seraient

  • Cela signifie que vous ne lancez pas réellement un lambda. Vous jetez un std::function, ce qui n’est pas vraiment ce que vous avez demandé ????
  • La création d'un std::function objet d'un lambda peut lever une exception
23
Michael Kenzel

Vous pouvez lancer et attraper un 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";
        }
}

Sortie:

lambda
catch
6
dave

Un lambda est un type anonyme unique. La seule façon de nommer le type d'une instance lambda consiste à la stocker dans une variable, puis à effectuer un decltype sur ce type de variable.

Vous pouvez attraper un lambda de plusieurs façons.

try  {
  throw []{};
} catch(...) {
}

dans ce cas, vous ne pouvez pas l'utiliser, sinon le relancer.

try  {
  throw +[]{};
} catch(void(*f)()) {
}

un lambda sans état peut être converti en un pointeur de fonction.

try  {
  throw std::function<void()>([]{});
} catch(std::function<void()> f) {
}

vous pouvez le convertir en std::function. L'inconvénient avec std::function est que le tas est alloué à de plus grandes lambdas, ce qui pourrait en théorie le faire jeter.

Nous pouvons éliminer cette allocation de tas:

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);
}

maintenant tu peux faire:

try {
  throw wrap_lambda<void()>([]{});
} catch( callable<void()> const& f ) {
}

callable est un effacement de type "poids plus léger" que std::function car cela ne peut pas provoquer l’allocation de nouvelle mémoire de tas.

Exemple en direct .

1