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).

Nawaz
źródło
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) {}.

kennytm
źródło
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.

Ise Wisteria
źródło
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.

Columbo
źródło
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ą.

Jon Koelzer
źródło