2014-09-06 11 views
7

È possibile dedurre l'arità di un lambda non generico accedendo al suo operator().Arity di un lambda generico

template <typename F> 
struct fInfo : fInfo<decltype(&F::operator())> { }; 

template <typename F, typename Ret, typename... Args> 
struct fInfo<Ret(F::*)(Args...)const> { static const int arity = sizeof...(Args); }; 

Questo è bello e dandy per qualcosa come [](int x){ return x; } come il operator() non è basato su modelli.

Tuttavia, lambda generici fare il template operator() ed è possibile solo per accedere a un'esemplificazione concreta del modello - che è un po 'problematico, perché non posso fornire manualmente argomenti di modello per il operator() come io non so quale sia la sua è l'arbitrio.

Così, naturalmente, qualcosa come

auto lambda = [](auto x){ return x; }; 
auto arity = fInfo<decltype(lambda)>::arity; 

non funziona.

Non so cosa trasmettere, né so quali argomenti del modello fornire (o quanti) (operator()<??>).
Qualche idea su come fare questo?

+4

Il 'operatore()' di lambda generici possono essere modelli variadic. Qual è la tua definizione di arità per quelli? –

+0

Perché vuoi chiedere a arity, e non "quanti' int's puoi accettare? "? – Yakk

risposta

2

È impossibile, poiché l'operatore di chiamata di funzione può essere un modello variadico. È stato impossibile farlo per sempre per gli oggetti funzione in generale, e i lambda con involucro speciale perché non erano ugualmente potenti sarebbe sempre stata una cattiva idea. Ora è il momento giusto per quella cattiva idea di tornare a casa a posatoio.

1

direi che questo è parzialmente possibile, almeno si può conoscere il arity complessiva (templato + tipi regolari) quando si crea un'istanza in modo esplicito i parametri auto di operator():

template <typename F, typename... Args> 
struct autofInfo : fInfo<decltype(&F::template operator()<Args...>)> {}; 

auto lambda = [](auto x, int y, float z) { return x + y + z; }; 

auto arity = autofInfo<decltype(lambda), int>::arity; 
//          ^^^ list of auto parameters instantiations 

assert(3 == arity); 
5

Questa tecnica lavorare in alcuni casi. Creo un tipo fake_anything in grado di simulare quasi tutto e provare a richiamare il tuo lambda con un certo numero di istanze.

#include <iostream> 

struct fake_anything { 
    fake_anything(fake_anything const&); 
    fake_anything(); 
    fake_anything&operator=(fake_anything const&); 
    template<class T>operator T&() const; 
    template<class T>operator T&&() const; 
    template<class T>operator T const&() const; 
    template<class T>operator T const&&() const; 
    fake_anything operator*() const; 
    fake_anything operator++() const; 
    fake_anything operator++(int) const; 
    fake_anything operator->() const; 
    template<class T>fake_anything(T&&); 
}; 
fake_anything operator+(fake_anything, fake_anything); 
fake_anything operator-(fake_anything, fake_anything); 
fake_anything operator*(fake_anything, fake_anything); 
fake_anything operator/(fake_anything, fake_anything); 
// etc for every operator 

template<class>using void_t=void; 
template<class Sig, class=void> 
struct can_invoke:std::false_type{}; 
template<class F, class...Args> 
struct can_invoke<F(Args...), 
    void_t< decltype(std::declval<F>()(std::declval<Args>()...)) > 
> : std::true_type 
{}; 

template<class Sig>struct is_sig:std::false_type{}; 
template<class R, class...Args>struct is_sig<R(Args...)>:std::true_type{}; 

template<unsigned...>struct indexes{using type=indexes;}; 
template<unsigned Max,unsigned...Is>struct make_indexes:make_indexes<Max-1,Max-1,Is...>{}; 
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{}; 
template<unsigned max>using make_indexes_t=typename make_indexes<max>::type; 

template<class T,unsigned>using unpacker=T; 

template<class F, class A, class indexes> 
struct nary_help; 
template<class F, class A, unsigned...Is> 
struct nary_help<F,A,indexes<Is...>>: 
    can_invoke<F(unpacker<A,Is>...)> 
{}; 
template<class F, unsigned N> 
struct has_n_arity: 
    nary_help<F, fake_anything, make_indexes_t<N>> 
{}; 

template<class F, unsigned Min=0, unsigned Max=10> 
struct max_arity{ 
    enum{Mid=(Max+Min)/2}; 
    enum{ 
    lhs = max_arity<F,Min,Mid>::value, 
    rhs = max_arity<F,Mid+1,Max>::value, 
    value = lhs>rhs?lhs:rhs, 
    }; 
}; 
template<class F, unsigned X> 
struct max_arity<F,X,X>: 
    std::integral_constant<int, has_n_arity<F,X>::value?(int)X:-1> 
{}; 

template<class F, unsigned Min=0, unsigned Max=10> 
struct min_arity{ 
    enum{Mid=(Max+Min)/2}; 
    enum{ 
    lhs = min_arity<F,Min,Mid>::value, 
    rhs = min_arity<F,Mid+1,Max>::value, 
    value = lhs<rhs?lhs:rhs, 
    }; 
}; 
template<class F, unsigned X> 
struct min_arity<F,X,X>: 
    std::integral_constant<unsigned,has_n_arity<F,X>::value?X:(unsigned)-1> 
{}; 

auto test1 = [](auto x, auto y)->bool { return x < y; }; 
auto test2 = [](auto x, auto y) { return x + y; }; 
auto test3 = [](auto x) { return x.y; }; 

int main() { 
    std::cout << can_invoke< decltype(test1)(fake_anything, fake_anything) >::value << "\n"; 
    std::cout << can_invoke< decltype(test1)(int, int) >::value << "\n"; 
    std::cout << has_n_arity< decltype(test1), 2 >::value << "\n"; 
    std::cout << max_arity< decltype(test1) >::value << "\n"; 
    std::cout << max_arity< decltype(test2) >::value << "\n"; 
    // will fail to compile: 
    // std::cout << max_arity< decltype(test3) >::value << "\n"; 
} 

live example.

Nota sufficiente SFINAE significherà quanto sopra otterrà il risultato sbagliato, come utilizzerà di operator., o l'uso di operator. su alcuni tipi di tipi di "derivati", oppure accedere ad alcuni tipi in base al largo del parametro fake_anything, ecc

Tuttavia, se lambda specifica il valore restituito con una clausola ->X, allora fake_anything è più che sufficiente. La parte difficile riguarda il corpo.

Si noti che questo approccio è spesso una cattiva idea, perché se si vuole conoscere l'arietà di una funzione, probabilmente sapete anche i tipi delle cose che si desidera richiamare l'oggetto funzione con! E sopra, rispondo a questa domanda molto facilmente (questo oggetto funzione può essere invocato con questi argomenti?). Può anche essere migliorato per chiedere "quale è il prefisso più lungo/più breve di questi argomenti che può richiamare questo oggetto funzione", o gestire "quante ripetizioni di tipo X funzionano per richiamare questo oggetto funzione" (se si desidera un errore pulito, si bisogno di un limite superiore).

+0

non sembra funzionare quando il corpo di lambda tenta di utilizzare il parametro 'auto' in un contesto non supportato da' fake_anything', ad es. 'return x * 2;' o 'return x.get();'; perché il compilatore valuta il corpo in 'can_invoke ' –

+0

@piotrs. ah sì, con la deduzione di tipo basato sul corpo che deve. Con '->' non dovrebbe avere motivo di farlo, ma ciò fa ancora schifo. Anche se eseguo l'override di ogni operatore, c'è ancora '.'. – Yakk

+0

@piotrs. alcuni miglioramenti. Funziona con 'x * 2', ma non' x.get() 'se' x' è di tipo 'auto'. – Yakk

0

Un'altra soluzione posible, per caso, quando sono noti i possibili tipi di modello: http://coliru.stacked-crooked.com/a/e3a07d723a8f27e9

using T1 = string; 
using T2 = int; 

std::integral_constant<int, 1> static arity(function<void(T1)>){ return {}; } 
std::integral_constant<int, 2> static arity(function<void(T1, T2)>){ return {}; } 

template<class Fn> 
using Arity = decltype(arity(Fn{})); 
Problemi correlati