ラムダのパラメーターの型と戻り値の型を理解することは可能ですか?


135

ラムダを考えると、それがパラメーターの型と戻り値の型を理解することは可能ですか?はいの場合、どのように?

基本的に、私はlambda_traitsこれを以下の方法で使用できるようにしたい:

auto lambda = [](int i) { return long(i*10); };

lambda_traits<decltype(lambda)>::param_type  i; //i should be int
lambda_traits<decltype(lambda)>::return_type l; //l should be long

背後にある動機は、lambda_traits引数としてラムダを受け入れる関数テンプレートで使用したいことであり、関数内のパラメーターの型と戻りの型を知る必要があります。

template<typename TLambda>
void f(TLambda lambda)
{
   typedef typename lambda_traits<TLambda>::param_type  P;
   typedef typename lambda_traits<TLambda>::return_type R;

   std::function<R(P)> fun = lambda; //I want to do this!
   //...
}

とりあえず、ラムダは引数を1つだけ取ると想定できます。

最初に、私は次のように作業しようとしstd::functionました:

template<typename T>
A<T> f(std::function<bool(T)> fun)
{
   return A<T>(fun);
}

f([](int){return true;}); //error

ただし、明らかにエラーが発生します。だから私はそれをTLambda関数テンプレートのバージョンに変更しstd::function、(上記のように)関数内にオブジェクトを構築したいと思います。


パラメータタイプがわかっている場合は、これを使用して戻り値のタイプを把握できます。しかし、パラメーターのタイプを理解する方法がわかりません。
Mankarse、2011年

関数は単一の引数を取ると想定されていますか?
iammilind 2011年

1
「パラメータ型」しかし、任意のラムダ関数にはパラメータ型がありません。パラメータはいくつでも取ることができます。したがって、すべての特性クラスは、位置インデックスによってパラメーターを照会するように設計する必要があります。
Nicol Bolas、2011年

@iammilind:はい。とりあえず、それは仮定できます。
Nawaz、2011年

@NicolBolas:当面は、ラムダが引数を1つだけ取ると想定できます。
Nawaz、2011年

回答:


160

おかしい、私はパラメータタイプを指定できるC ++ 0xのラムダでテンプレートを特殊化するfunction_traitsことに基づく実装を書いたところです。その質問の回答で説明されているトリックは、ラムダのを使用することです。 decltypeoperator()

template <typename T>
struct function_traits
    : public function_traits<decltype(&T::operator())>
{};
// For generic types, directly use the result of the signature of its 'operator()'

template <typename ClassType, typename ReturnType, typename... Args>
struct function_traits<ReturnType(ClassType::*)(Args...) const>
// we specialize for pointers to member function
{
    enum { arity = sizeof...(Args) };
    // arity is the number of arguments.

    typedef ReturnType result_type;

    template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...>>::type type;
        // the i-th argument is equivalent to the i-th tuple element of a tuple
        // composed of those arguments.
    };
};

// test code below:
int main()
{
    auto lambda = [](int i) { return long(i*10); };

    typedef function_traits<decltype(lambda)> traits;

    static_assert(std::is_same<long, traits::result_type>::value, "err");
    static_assert(std::is_same<int, traits::arg<0>::type>::value, "err");

    return 0;
}

このソリューションはのような一般的なラムダでは機能しないことに注意してください[](auto x) {}


えっと、私はこれを書いていました。tuple_elementしかし、考えていませんでした、ありがとう。
GManNickG 2011年

@GMan:あなたのアプローチがこれとまったく同じではない場合は、それを投稿してください。このソリューションをテストします。
Nawaz、2011年

3
完全な特性はconst、宣言されたラムダmutable[]() mutable -> T { ... })に対して、non-の特殊化も使用します。
Luc Danton、2011年

1
@Andry operator()は、この実装ではない(潜在的に)複数のオーバーロードを持つ関数オブジェクトの根本的な問題です。autoはタイプではないため、これに対する答えにはなりませんtraits::template arg<0>::type
Caleth

1
@ helmesjosf.net / p / tacklelib / tacklelib / HEAD / tree / trunk / include / tacklelib / リンク切れの解決策として、ルートのルークから検索してみてください。
Andry

11

これが厳密に標準に準拠していることはわかりませんが、 ideoneは次のコードをコンパイルしました。

template< class > struct mem_type;

template< class C, class T > struct mem_type< T C::* > {
  typedef T type;
};

template< class T > struct lambda_func_type {
  typedef typename mem_type< decltype( &T::operator() ) >::type type;
};

int main() {
  auto l = [](int i) { return long(i); };
  typedef lambda_func_type< decltype(l) >::type T;
  static_assert( std::is_same< T, long( int )const >::value, "" );
}

ただし、これは関数タイプのみを提供するため、結果タイプとパラメータータイプをそこから抽出する必要があります。あなたが使用できる場合boost::function_traitsresult_typeおよびarg1_type 目的を満たします。ideoneはC ++ 11モードでブーストを提供しないようなので、実際のコードを投稿できませんでした。


1
良いスタートだと思います。+1。次に、必要な情報を抽出するために関数タイプを操作する必要があります。(私はものを学びたいので、今のところブーストを使いたくありません)。
Nawaz、2011年

6

@KennyTMsの回答に示されている特殊化メソッドは、可変および可変ラムダを含むすべてのケースをカバーするように拡張できます。

template <typename T>
struct closure_traits : closure_traits<decltype(&T::operator())> {};

#define REM_CTOR(...) __VA_ARGS__
#define SPEC(cv, var, is_var)                                              \
template <typename C, typename R, typename... Args>                        \
struct closure_traits<R (C::*) (Args... REM_CTOR var) cv>                  \
{                                                                          \
    using arity = std::integral_constant<std::size_t, sizeof...(Args) >;   \
    using is_variadic = std::integral_constant<bool, is_var>;              \
    using is_const    = std::is_const<int cv>;                             \
                                                                           \
    using result_type = R;                                                 \
                                                                           \
    template <std::size_t i>                                               \
    using arg = typename std::tuple_element<i, std::tuple<Args...>>::type; \
};

SPEC(const, (,...), 1)
SPEC(const, (), 0)
SPEC(, (,...), 1)
SPEC(, (), 0)

デモ

アリアリティは可変個operator()のs に対して調整されないことに注意してください。代わりに検討することもできis_variadicます。


1

@KennyTMsによって提供される答えはうまく機能しますが、ラムダにパラメーターがない場合、インデックスarg <0>を使用してもコンパイルされません。他の誰かがこの問題を抱えていた場合、私は簡単な解決策を持っています(つまり、SFINAE関連の解決策を使用するよりも簡単です)。

可変個引数タイプの後のarg構造体のタプルの最後にvoidを追加するだけです。すなわち

template <size_t i>
    struct arg
    {
        typedef typename std::tuple_element<i, std::tuple<Args...,void>>::type type;
    };

アリティはテンプレートパラメータの実際の数に依存しないため、実際の値は不正確にならず、0の場合でも少なくともarg <0>が存在し、それを使用して何を実行してもかまいません。すでにインデックスを超えないことを計画している場合はarg<arity-1>、それが現在の実装に干渉することはありません。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.