Czy można określić typ parametru i zwracany typ lambdy?


135

Czy biorąc pod uwagę lambdę, można określić typ jej parametru i typ zwracany? Jeśli tak, w jaki sposób?

Zasadniczo chcę, aby lambda_traitsmożna było używać na następujące sposoby:

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

Motywacją jest to, że chcę użyć lambda_traitsw szablonie funkcji, który akceptuje lambdę jako argument, i muszę znać typ parametru i typ zwracany wewnątrz funkcji:

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!
   //...
}

Na razie możemy założyć, że lambda przyjmuje dokładnie jeden argument.

Początkowo starałem się współpracować z std::function:

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

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

Ale to oczywiście powodowałoby błąd. Więc zmieniłem go na TLambdawersję szablonu funkcji i chcę skonstruować std::functionobiekt wewnątrz funkcji (jak pokazano powyżej).


Jeśli znasz typ parametru następnie ten może być używany, aby dowiedzieć się typ zwracany. Nie wiem jednak, jak określić typ parametru.
— Mankarse

Czy zakłada się, że funkcja przyjmuje jeden argument?
— iammilind

1
„typ parametru” Jednak dowolna funkcja lambda nie ma typu parametru. Może przyjąć dowolną liczbę parametrów. Zatem każda klasa cech musiałaby być zaprojektowana do odpytywania parametrów za pomocą indeksów pozycji.
— Nicol Bolas

@iammilind: Tak. na razie możemy to założyć.
— Nawaz

@NicolBolas: Na razie możemy założyć, że lambda przyjmuje dokładnie jeden argument.
— Nawaz

Odpowiedzi:


160

Zabawne, właśnie napisałem function_traitsimplementację opartą na specjalizacji szablonu na lambdzie w C ++ 0x, która może podać typy parametrów. Sztuczka, jak opisano w odpowiedzi na to pytanie, polega na użyciu lambda . 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;
}

Zauważ, że to rozwiązanie nie działa dla generycznych, takich jak lambda [](auto x) {}.


Heh, właśnie to pisałem. Nie myślałem o tym tuple_element, dzięki.
— GManNickG

@GMan: Jeśli twoje podejście nie jest dokładnie takie samo, proszę opublikuj to. Mam zamiar przetestować to rozwiązanie.
— Nawaz

3
Pełna cecha również używałaby specjalizacji dla non- const, dla tych deklarowanych lambda mutable( []() mutable -> T { ... }).
— Luc Danton

1
@Andry to podstawowy problem z obiektami funkcji, które mają (potencjalnie) wiele przeciążeń lub operator()nie z tą implementacją. autonie jest typem, więc nigdy nie może być odpowiedzią natraits::template arg<0>::type
— Caleth

1
@helmesjo sf.net/p/tacklelib/tacklelib/HEAD/tree/trunk/include/tacklelib/ ... Jako rozwiązanie dla zepsutych linków: spróbuj szukać z katalogu głównego, Luke.
— Andry,

11

Chociaż nie jestem pewien, czy jest to ściśle zgodne ze standardami, ideone skompilowało następujący kod:

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

Jednak zapewnia to tylko typ funkcji, więc wyniki i typy parametrów muszą zostać wyodrębnione z niego. Jeśli można użyć boost::function_traits, result_typea arg1_type spotka się z celem. Ponieważ ideone wydaje się nie zapewniać przyspieszenia w trybie C ++ 11, nie mogłem opublikować rzeczywistego kodu, przepraszam.


1
Myślę, że to dobry początek. +1 za to. Teraz musimy popracować nad typem funkcji, aby wyodrębnić wymagane informacje. (Nie chcę teraz używać Boost, ponieważ chcę się nauczyć wszystkich rzeczy).
— Nawaz

6

Metodę specjalizacji pokazaną w odpowiedzi @KennyTM można rozszerzyć tak, aby obejmowała wszystkie przypadki, w tym zmienne i zmienne lambdy:

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)

Demo .

Należy zauważyć, że arancja nie jest dostosowana do wariadycznych operator(). Zamiast tego można również rozważyć is_variadic.


1

Odpowiedź udzielona przez @KennyTMs działa świetnie, jednak jeśli lambda nie ma parametrów, użycie indeksu arg <0> nie jest kompilowane. Jeśli ktoś inny miał ten problem, mam proste rozwiązanie (to znaczy prostsze niż używanie rozwiązań powiązanych z SFINAE).

Po prostu dodaj void na końcu krotki w strukturze arg po różnych typach argumentów. to znaczy

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

ponieważ liczba nie zależy od faktycznej liczby parametrów szablonu, rzeczywista nie będzie niepoprawna, a jeśli wynosi 0, to przynajmniej arg <0> będzie nadal istniał i możesz z nim zrobić, co chcesz. Jeśli planujesz już nie przekraczać indeksu, arg<arity-1>nie powinno to kolidować z bieżącą implementacją.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.