2017-05-09 1 views
4

私は、指定されたパラメータ型のセットで呼び出すことができるかどうかを、一般的なラムダ型を指定してコンパイル時に決定したいと考えています。私は次の例C++ 14の実装を持っています:コンパイル時に、指定されたパラメータ型のセットで汎用ラムダを正常に呼び出すことができるかどうかを検出する方法はありますか?

#include <iostream> 

// helper function; this overload handles the case that the call is possible 
// use SFINAE with the extra template parameter to remove this from consideration when the 
// call is ill-formed 
template <typename Func, typename... Args, typename = decltype(std::declval<Func>()(std::declval<Args>()...))> 
auto eval(Func f, Args &&... args) { return f(args...); } 

// special type returned from `eval()` when the call can't be done 
struct invalid_call { }; 

// helper function; this overload handles the case that the call is not possible 
template <typename Func> 
invalid_call eval(Func f, ...) { return invalid_call{}; }; 

// bring in std::negation from C++17 to help create the below trait 
template<class B> 
struct negation : std::integral_constant<bool, !bool(B::value)> { }; 

// trait that determines whether `Func` can be invoked with an argument list of types `Args...` 
template <typename Func, typename... Args> 
using can_call = negation<std::is_same<decltype(eval(std::declval<Func>(), std::declval<Args>()...)), invalid_call>>; 

// arbitary type that has no `operator+` 
struct foo {}; 

int main() 
{ 
    auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; }; 
    using FuncType = decltype(func); 

    std::cout << "can call with (int, int): " << can_call<FuncType, int, int>::value << std::endl; 
    std::cout << "can call with (foo, foo): " << can_call<FuncType, foo, foo>::value << std::endl; 
} 

この例は現状どおりに動作します。 、末尾の戻り値の型がC++14's deduced return types don't work with SFINAEため、指定する必要がありますされて

auto func = [](auto a1, auto a2) -> decltype(a1 + a2) { return a1 + a2; }; 

:私は好きではありませんはラムダが宣言されなければならないという面倒な方法です。戻り型の控除では、引数リストの型を呼び出し可能なテンプレート呼び出し演算子に代入する必要があり、エラーが発生した場合はプログラムが不正です。

理想的には、私は次の操作を行うことができるだろう:

auto func = [](auto a1, auto a2) { return a1 + a2; }; 

と戻り値の型が自動的にワークアウトしましょう。これは私のユーザーに提供する最も直感的なインターフェイスになります。これは非常に単純な例なので、decltype()への引数は悪く見えませんが、実際にはラムダはこのアプローチではうまくいかないいくつかのステートメントになる可能性があります。だから私の質問は:

最新のC++技術(C++ 14が最適かもしれませんが、私は必要に応じて新しい機能も公開しています)を使って、コンパイル時にテストできる方法はありますか一般的なラムダは、パラメータ型の任意のリストで呼び出すことができますか?

+1

あなたの疑問に答えていただいたようです:いいえ、後ろ向き戻りタイプなしではありません。 – Barry

+0

@バリー:私はそうかもしれないと思ったが、現代のC++の知識は正式なものではないので、そこに他のトリックがあるかどうかを見たいと思った。末尾の戻り値型の必要性によって制限されている場合、ラムダは複数のステートメントを持つことができないため、このアプローチはあまり有用ではありません。 –

+0

リターン型の控除では、ラムダがそれぞ​​れの控除時に同じ戻り型を推論する必要があります。後続の戻り型decltypeとして最初のreturn文(または最も単純なもの)を使用できます。それは実際の解決策ではありませんが、問題のスペースを削減します。 – TBBle

答えて

3

マクロのC++ 98機能

#define RETURNS(...) noexcept(noexcept(__VA_ARGS__))->decltype(__VA_ARGS__) { return __VA_ARGS__; } 

その後、

auto func = [](auto a1, auto a2) RETURNS(a1+a2); 

はそれをしない使用して、確かに。

マクロを使用せずに、この

auto func = [](auto a1, auto a2) => a1+a2; 

になり、私たちの非常に自身@BarryによってC++ 20の提案があります。

一般に、SFINAE式が受け入れられるかどうかを判断するために、関数やラムダの本体を強制的にコンパイルすることはできませんし、可能でもありません。このようなエラーは難しいと思われます。これはC++コンパイラの作業を単純化するためです。過負荷の解決が成功するかどうかを判断しながら、任意の機能の本体全体をコンパイルしてから、エラーのない状態に完全に戻す必要はありません。

複数のreturn文またはreturn文で使用される複合型の長いセットの場合、あなたは不運です。 decltypeと書いてください。あなたが正しいように祈ってください。

+0

... yuck。 upvoteを持って、それについてもう一度話しません:p – Quentin

関連する問題