Можно ли поймать исключение типа лямбда? - PullRequest
0 голосов
/ 05 декабря 2018

Несмотря на то, что рекомендуется создавать только исключения типов, производных от класса std::exception, C ++ позволяет генерировать все что угодно.Все приведенные ниже примеры являются допустимыми 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!

Последний пример интересен, поскольку он потенциально позволяет передавать некоторый код для выполнения на узле перехвата без определения отдельного класса или функции.

Но возможно ли вообще поймать лямбду (или замыкание)?catch ([]{} e) не работает.

Ответы [ 4 ]

0 голосов
/ 10 декабря 2018

Лямбда - это уникальный анонимный тип.Единственный способ назвать тип лямбда-экземпляра - это сохранить его в переменной, а затем сделать decltype для этого типа переменной.

Существует несколько способов поймать брошенную лямбду.

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

в этом случае вы не можете использовать его, кроме как бросить его снова.

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

лямбда без сохранения состояния может быть преобразована в указатель функции.

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

вы можетепреобразовать его в std::function.Недостатком std::function является то, что он выделяет кучу для больших лямбд, что теоретически может вызвать его выброс.

Мы можем устранить это выделение кучи:

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

теперь вы можете сделать:

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

callable - это стирание типа «меньшего веса», чем std::function, поскольку оно не может быть причиной выделения новой памяти кучи.

Пример в реальном времени .

0 голосов
/ 05 декабря 2018

Обработчики исключений сопоставляются на основе типа, и неявные преобразования, выполняемые для сопоставления объекта исключения с обработчиком, более ограничены, чем в других контекстах.

Каждое лямбда-выражение представляет тип замыкания, уникальный дляокружающая сфераТаким образом, ваша наивная попытка не может сработать, поскольку []{} имеет совершенно другой тип в выражении броска и обработчике!

Но вы правы.C ++ позволяет бросать любой объект.Таким образом, если вы явно преобразуете лямбда-код в тип, соответствующий обработчику исключений, это позволит вам вызвать этот произвольный вызываемый объект.Например:

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

Это может иметь интересную полезность, но я бы предостерег от бросания вещей, не производных от std::exception.Лучшим вариантом, вероятно, будет создание типа, производного от std::exception и содержащего вызываемый объект.

0 голосов
/ 05 декабря 2018

C ++ позволяет бросать все что угодно.И это позволяет вам ловить все, что вы бросаете.Можно, конечно, кинуть лямбду.Единственная проблема заключается в том, что для того, чтобы поймать что-либо, вам нужно знать тип или хотя бы родительский тип этого чего-либо.Поскольку лямбды не происходят из общей базы, вы должны знать тип вашей лямбды, чтобы поймать лямбду.Основная проблема заключается в том, что каждое лямбда-выражение даст вам значение отличного типа .Это означает, что и ваш бросок, и ваш улов должны основываться на одном и том же лямбда-выражении (примечание: одно и то же выражение, а не просто какое-то выражение, которое выглядит одинаково).Один из способов сделать эту работу в некоторой степени - заключить инкапсуляцию создания лямбда-функции в функцию.Таким образом, вы можете вызвать функцию в вашем выражении throw и использовать тип возвращаемого значения функции для вывода типа на 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);
    }
}

Попробуйте это здесь .

Вы также можете просто использовать std::function, как предложено в некоторых других ответах, что потенциально является более практичным подходом.Недостатки этого, однако, будут

  • Это означает, что вы на самом деле не выбрасываете лямбду.Вы бросаете std::function, что на самом деле не то, что вы просили 101
  • Создание std::function объекта из лямбды может вызвать исключение
0 голосов
/ 05 декабря 2018

Можно бросить и поймать 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";
        }
}

Вывод:

lambda
catch
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...