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)
は機能しません。
例外ハンドラは型に基づいて照合され、例外オブジェクトをハンドラに照合するために行われる暗黙的な変換は、他のコンテキストよりも制限されます。
各ラムダ式は、周囲のスコープに固有のクロージャータイプを導入します。 []{}
にはthrow式とハンドラーに完全に異なるタイプが含まれているため、単純な試みは機能しません!
しかし、あなたは正しいです。 C++では、任意のオブジェクトをスローできます。したがって、事前にラムダを明示的に例外ハンドラに一致する型に変換すると、その任意の呼び出し可能オブジェクトを呼び出すことができます。例えば:
try {
throw std::function<void()>{ []{} }; // Note the explicit conversion
} catch(std::function<void()> const& f) {
f();
}
これには興味深いユーティリティがあるかもしれませんが、std::exception
から派生したもの以外のものをスローすることには注意してください。おそらく、std::exception
から派生し、呼び出し可能オブジェクトを保持できる型を作成することをお勧めします。
C++では、何でも投げることができます。そして、投げたものは何でもキャッチできます。もちろん、ラムダを投げることができます。唯一の問題は、何かをキャッチするには、その何かのタイプまたは少なくとも親タイプを知る必要があるということです。ラムダは共通のベースから派生するものではないため、ラムダをキャッチするにはラムダのタイプを知る必要があります。それに関する主な問題は、すべてのラムダ式が distinct type の右辺値を与えることです。これは、スローとキャッチの両方が同じラムダ式に基づいている必要があることを意味します(注:まったく同じように見えるいくつかの式ではなく、同じ式)。この作業をある程度行うために考えられる方法の1つは、ラムダの作成をカプセル化して関数をスローすることです。そのようにして、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
をスローしますが、これは本当にあなたが要求したものではありませんか????std::function
オブジェクトの作成 例外をスローできる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
ラムダは一意の匿名型です。ラムダインスタンスの型に名前を付ける唯一の方法は、変数に格納してから、その変数型で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
よりも「軽い」タイプの消去です。
ライブの例 。