17

Ho il seguente pezzo di codice (C++ 11):È possibile restituire un lambda variadico da un modello di funzione?

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; 
} 

che restituisce in sostanza la tesi result che ha prodotto il valore minimo per l'espressione f(result). Questo può essere chiamato in questo modo:

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 

Ora vorrei utilizzare questo in un modo 'curry' in modo che io possa avere un lambda variadic da min_on e poi chiamare con argomenti (che io possa ricevere in seguito) , così:

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

È possibile?

+0

Richiede solo C++ 11? – Yakk

+0

@Yakk sì, ma si possono citare anche le soluzioni C++ 14 – Patryk

risposta

12

In C++ 11, le seguenti opere, se siete disposti a creare manualmente l'oggetto funzione:

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}; 
} 

E poi la chiamano:

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

Per utilizzare lambda in C + +14, è necessario omettere il tipo di ritorno finale perché non è possibile specificare il tipo di lambda senza assegnarlo prima a una variabile, perché 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

È possibile restituire lambda variadico da una funzione in C++ 14: basta omettere il tipo di ritorno finale - sarà dedotto. – Eugene

+0

@Eugene Duh, ovviamente. Mi dimentico sempre di questo. –

+0

@KonradRudolph + Inoltre in C++ 14 puoi usare 'std :: common_type_t'. Si prega di contrassegnare esplicitamente la prima soluzione come C++ 11 in modo che le persone possano vedere le differenze nei 2 approcci. – Patryk

7

Non sono sicuro su C++ 11, ma in C++ 14, è possibile creare una lambda per avvolgere la funzione:

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

Si noti che questa funzione 'auto' arg è ben supportata anche su compilatori per lo più C++ 11. – Yakk

3

In C + +14 questo è facile.

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 
    }; 
} 

molti compilatori ottenuto tipo auto ritorno deduzione e argomenti in lambda lavorativi prima della completa C++ 14 di sostegno. Quindi un nominale C++ 11 compilatore potrebbe essere in grado di compilare questo:

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 
    }; 
} 

in 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)...); 
    } 
}; 

è boilerplate. Questo ci consente di superare l'intero set di sovraccarico di min_on_impl come un unico oggetto.

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

ci dà 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)); 
} 

è modulare e risolve il problema: entrambi min_on_helper e bind_1st può essere testato in modo indipendente.

È inoltre possibile sostituire bind_1st con una chiamata a std::bind, ma nella mia esperienza le stranezze di std::bind mi fanno estremamente cauto nel raccomandarlo a chiunque.

+0

Perché la differenza da C++ 14 a C++ 1z? – Barry

+0

@Barry perché il compilatore crappy su cui ho lavorato molto su C++ 14 non aveva i tipi di ritorno 'auto' quando aveva implementato gran parte del C++ 14. Versioni modificate – Yakk

+0

Non dovrebbe quel 'decltype (min_on_impl (std :: declval () ...)' essere 'decltype (min_on_impl (std :: forward (args) ...)', il decltype deve essere inoltrato per corrispondere al chiamata effettiva? Potrebbe produrre errori se ci sono sovraccarichi che variano sul lvalue/valore dei parametri – Niall