17

Mam następujący fragment kodu (C++ 11):Czy można zwrócić lambdę variadic z szablonu funkcji?

template <typename F, 
      typename FirstT, 
      typename... FIn> 
auto min_on(F f, FirstT first, FIn... v) -> typename std::common_type<FirstT, FIn...>::type 
{ 
    using rettype = typename std::common_type<FirstT, FIn...>::type; 
    using f_rettype = decltype(f(first)); 

    rettype result = first; 
    f_rettype result_trans = f(first); 
    f_rettype v_trans; 
    (void)std::initializer_list<int>{ 
     ((v_trans = f(v), v_trans < result_trans) 
      ? (result = static_cast<rettype>(v), result_trans = v_trans, 0) 
      : 0)...}; 
    return result; 
} 

które zasadniczo zwraca argument result że wytwarzany minimalną wartość wyrażenia f(result). Można to nazwać tak:

auto mod7 = [](int x) 
{ 
    return x % 7; 
}; 

auto minimum = min_on(mod7, 2, 8, 17, 5); 
assert(minimum == 8); // since 8%7 = 1 -> minimum value for all arguments passed 

Teraz chciałbym użyć tego w „curry” sposób, aby można było uzyskać zmiennej liczbie argumentów lambda od min_on a następnie wywołać go z argumentami (że mogę otrzymać później) , tak:

auto mod7 = [](int x) 
{ 
    return x % 7; 
}; 

auto f_min = min_on(mod7); 
auto minimum = f_min(2, 8, 17, 5); 
// or 
auto minimum = min_on(mod7)(2, 8, 17, 5); 

Czy to możliwe?

+0

Czy potrzebujesz tylko C++ 11? – Yakk

+0

@Tak tak, ale możesz również wymienić rozwiązania C++ 14 – Patryk

Odpowiedz

12

w C++ 11, następujące prace, jeśli jesteś gotów do ręcznie utworzyć obiekt funkcję:

template <typename F> 
struct min_on_t { 
    min_on_t(F f) : f(f) {} 

    template <typename T, typename... Ts> 
    auto operator()(T x, Ts... xs) -> typename std::common_type<T, Ts...>::type 
    { 
     // Magic happens here. 
     return f(x); 
    } 

    private: F f; 
}; 

template <typename F> 
auto min_on(F f) -> min_on_t<F> 
{ 
    return min_on_t<F>{f}; 
} 

a następnie wywołać go:

auto minimum = min_on(mod7)(2, 8, 17, 5); 

Aby korzystać lambdy w C + +14, musisz pominąć końcowy typ powrotu, ponieważ nie możesz określić typu lambda bez wcześniejszego przypisania jej do zmiennej, ponieważ a lambda expression cannot occur in an unevaluated context.

template <typename F> 
auto min_on(F f) 
{ 
    return [f](auto x, auto... xs) { 
     using rettype = std::common_type_t<decltype(x), decltype(xs)...>; 
     using f_rettype = decltype(f(x)); 

     rettype result = x; 
     f_rettype result_trans = f(x); 
     (void)std::initializer_list<int>{ 
      (f(xs) < result_trans 
       ? (result = static_cast<rettype>(xs), result_trans = f(xs), 0) 
       : 0)...}; 
     return result; 
    }; 
} 
+0

Możliwe jest zwrócenie variadic lambda z funkcji w C++ 14: po prostu pomiń końcowy typ powrotu - zostanie on wywnioskowany. – Eugene

+0

@Eugene Duh, oczywiście. Zawsze o tym zapominam. –

+0

@KonradRudolph + dodatkowo w C++ 14 możesz użyć 'std :: common_type_t'. Proszę zaznaczyć jednoznacznie pierwsze rozwiązanie jako C++ 11, aby ludzie mogli dostrzec różnice w 2 podejściach. – Patryk

7

Nie wiem na C++ 11, ale w C++ 14, można utworzyć lambda owinąć funkcję w:

auto min_on_t = [](auto f) { 
    return [=](auto ... params) { 
     return min_on(f, params...); 
    }; 
}; 

auto min_t = min_on_t(mod7); 
auto minimum = min_t(2, 8, 17, 5); 

Live on Coliru

+0

Należy zauważyć, że ta funkcja 'auto' arg jest dobrze obsługiwana nawet w większości kompilatorów C++ 11. – Yakk

3

W C + +14 to jest łatwe.

template<class F> 
auto min_on(F&& f) { 
    return [f=std::forward<F>(f)](auto&& arg0, auto&&...args) { 
    // call your function here, using decltype(args)(args) to perfect forward 
    }; 
} 

Wiele kompilatorów drogę powrotną typu auto odliczenia i argumentów na lambdas roboczych przed 14 Pełna obsługa C++. Więc nominalnej C++ 11 Kompilator może być w stanie skompilować to:

auto min_on = [](auto&& f) { 
    return [f=decltype(f)(f)](auto&& arg0, auto&&...args) { 
    // call your function here, using decltype(args)(args) to perfect forward 
    }; 
} 

w C++ 11:

struct min_on_helper { 
    template<class...Args> 
    auto operator()(Args&&...args) 
    -> decltype(min_on_impl(std::declval<Args>()...)) 
    { 
    return min_on_impl(std::forward<Args>(args)...); 
    } 
}; 

jest boilerplate. Dzięki temu możemy przekazać cały zestaw przeciążeniowy o wartości min_on_impl jako jeden obiekt.

template<class F, class T> 
struct bind_1st_t { 
    F f; 
    T t; 
    template<class...Args> 
    typename std::result_of<F&(T&, Args...)>::type operator()(Args&&...args)&{ 
    return f(t, std::forward<Args>(args)...); 
    } 
    template<class...Args> 
    typename std::result_of<F const&(T const&, Args...)>::type operator()(Args&&...args)const&{ 
    return f(t, std::forward<Args>(args)...); 
    } 
    template<class...Args> 
    typename std::result_of<F(T, Args...)>::type operator()(Args&&...args)&&{ 
    return std::move(f)(std::move(t), std::forward<Args>(args)...); 
    } 
}; 
template<class F, class T> 
bind_1st_t< typename std::decay<F>::type, typename std::decay<T>::type > 
bind_1st(F&& f, T&& t) { 
    return {std::forward<F>(f), std::forward<T>(t)}; 
} 

daje nam bind_1st.

template<class T> 
auto min_on(T&& t) 
-> decltype(bind_1st(min_on_helper{}, std::declval<T>())) 
{ 
    return bind_1st(min_on_helper{}, std::forward<T>(t)); 
} 

jest modułowa i rozwiązuje problem: zarówno min_on_helper i bind_1st można badać niezależnie.

Możesz również zastąpić bind_1st dzwoniąc pod numer std::bind, ale z mojego doświadczenia wynika, że ​​dziwactwa z std::bind powodują, że jestem bardzo ostrożny, zalecając to każdemu.

+0

Dlaczego różnica od C++ 14 do C++ 1z? – Barry

+0

@ Barka, ponieważ komplikowany kompilator, nad którym pracowałem w C++ 14, nie miał zwrotów 'auto', gdy miał zaimplementowany duży C++ 14. Edytowane wersje. – Yakk

+0

Nie należy tego 'decltype (min_on_impl (std :: declval () ...)' be 'decltype (min_on_impl (std :: forward (args) ...) ', decltype powinno być do przodu, aby dopasować faktyczne wywołanie? Mogłoby generować błędy, jeśli istnieją przeciążenia, które różnią się od l/wartość/parametrów parametrów. – Niall

Powiązane problemy