c++ - functional - std::function




¿Es posible atrapar una excepción del tipo lambda? (3)

C ++ te permite lanzar cualquier cosa. Y te permite atrapar lo que lances. Usted puede, por supuesto, lanzar una lambda. El único problema es que, para atrapar algo, necesitas saber el tipo o al menos el tipo padre de ese algo. Como las lambdas no se derivan de una base común, debe conocer el tipo de su lambda para capturar una lambda. El principal problema con eso es que cada expresión lambda le dará un valor de un tipo distinto . Eso significa que tanto tu lanzamiento como tu captura deben estar basados ​​en la misma expresión lambda (nota: la misma expresión, no solo una expresión que se ve exactamente igual). Una forma en la que se me ocurre hacer este trabajo hasta cierto punto sería encapsular la creación de la lambda para lanzar una función. De esa manera, puede llamar a la función en su expresión de throw y usar el tipo de retorno de la función para deducir el tipo de 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);
    }
}

Pruébalo here .

También puede usar la std::function como se sugiere en algunas de las otras respuestas, lo cual es potencialmente un enfoque más práctico. Las desventajas de eso, sin embargo, serían

  • Significa que en realidad no tiras un lambda. Tiras una std::function , que no es realmente lo que pediste
  • La creación de un objeto std::function desde un lambda puede generar una excepción.

Si bien es una buena práctica lanzar solo excepciones de tipos derivados de la clase std::exception , C ++ hace posible lanzar cualquier cosa. Todos los ejemplos a continuación son válidos 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!

El último ejemplo es interesante, ya que potencialmente permite que se pase algún código para ejecutarse en el sitio de captura sin tener que definir una clase o función separada.

Pero, ¿es posible atrapar un lambda (o un cierre)? catch ([]{} e) no funciona.


Los controladores de excepciones se comparan según el tipo, y las conversiones implícitas realizadas para hacer coincidir un objeto de excepción con un controlador son más limitadas que en otros contextos.

Cada expresión lambda introduce un tipo de cierre que es único para el ámbito que lo rodea. Por lo tanto, su intento ingenuo no puede funcionar, ya que []{} tiene un tipo completamente diferente en la expresión de lanzamiento y en el controlador.

Pero tienes razón. C ++ te permite lanzar cualquier objeto. Por lo tanto, si convierte explícitamente el lambda de antemano en un tipo que coincida con un controlador de excepciones, le permitirá llamar a ese llamamiento arbitrario. Por ejemplo:

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

Esto puede tener una utilidad interesante, pero le advierto que no arroje cosas que no se deriven de std::exception . Una mejor opción probablemente sería crear un tipo que se derive de std::exception y pueda contener un llamable.


Un lambda es un tipo anónimo único. La única manera de nombrar un tipo de instancia lambda es almacenarlo en una variable, luego hacer un decltype en ese tipo de variable.

Hay algunas formas en que puedes atrapar un lambda arrojado.

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

en este caso no puedes usarlo, aparte de tirarlo de nuevo.

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

un lambda sin estado se puede convertir en un puntero de función.

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

Puedes convertirlo en una std::function . La desventaja de la std::function es que las acumulaciones de montón para lambdas más grandes, lo que en teoría podría hacer que se lance.

Podemos eliminar esa asignación de pila:

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

ahora puedes hacer

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

callable es un borrado de tipo "más ligero" que std::function ya que no puede hacer que se asigne nueva memoria de almacenamiento dinámico.

Ejemplo vivo .





lambda