2016-05-31 18 views
6

Dato il tipo di una funzione callable C, voglio ottenere in fase di compilazione uno std::function; il tipo di cui:Creare un tipo di funzione std :: con argomenti limitati

  • ha lo stesso tipo di ritorno della funzione C
  • ai tipi di argomenti sono i primi N tipi di argomenti di funzione C

Ciò significa che, per un dato tipo void(int, char, double) e un dato N, il tipo della funzione è:

  • N = 1 => tipo di risultato: std::function<void(int)>
  • N = 2 => tipo di risultato: std::function<void(int, char)>
  • N = 3 => tipo di risultato: std::function<void(int, char, double)>
  • N > 3 => errore di compilazione tempo

Esempio:

template<std::size_t N, typename R, typename... A> 
constexpr auto get() { 
    return /*(magically somehow)*/ std::function<R(FirstNFromA...)> 
} 

template<std::size_t N, typename R, typename... A> 
struct S { 
    using func = decltype(get<N, R, A...>()); 
}; 
+0

Nice Q & its solution. Sembra una delizia di codifica. Tuttavia, non riesco a capire, dove sarà usato praticamente? In una vera applicazione, perché qualcuno vorrebbe usare 'FuncType <2, void (int, char, double, int)> :: Type' invece di semplice' std :: function '! Anche all'interno dei modelli, non sembra esserci la necessità di fare una cosa del genere. Per risolvere un problema difficile ci sono 2 modi: o ricavare una soluzione altrettanto dura o semplificare il problema. :-) – iammilind

+0

@iammilind Ho avuto il problema durante un tentativo di progettare qualcosa con un mucchio di mixin. Uno dei mixin ottiene una lista variadica di tipi, l'ultimo dei quali è quello da cui ereditare. Va da sé che il primo 'N-1' deve essere usato come argomento per un tipo di funzione. :-) – skypjack

risposta

6

Segue una possibile soluzione:

L'idea di base è utilizzare uno tuple e quelle utilità che fanno parte della libreria di modelli standard (ad esempio, std::tuple_element), mescolarle con un'espansione di pacchetto posizionata nel posto giusto e basta.
Per fare ciò, ho usato una funzione constexpr che restituisce un oggetto vuoto std::function del tipo specificato. Quindi il tipo di funzione viene acquisito tramite un decltype ed esportato come un tipo dell'oggetto FuncType utilizzando un alias.
Tutto avviene al momento della compilazione.
Per dedurre il tipo giusto per quella funzione, ho utilizzato uno schema ben noto che coinvolge uno std::integer_sequence e in realtà scompatta i tipi di una tupla espandendo gli indici.

+0

Siamo in C++ 14-land, basta usare 'tuple_element_t'. –

+0

@ T.C. Ha senso. Fisso. – skypjack

1

Un'altra soluzione potrebbe essere:

#include <tuple> 
#include <utility> 
#include <functional> 
#include <type_traits> 

template <size_t N, class R, class Pack, class ResultPack, class Voider> 
struct FuncTypeImpl; 

template <size_t N, class R, template <class...> class Pack, class First, class... Args, class... ResultArgs> 
struct FuncTypeImpl<N, R, Pack<First, Args...>, Pack<ResultArgs...>, std::enable_if_t<(N > 0)>>: FuncTypeImpl<N-1, R, Pack<Args...>, Pack<ResultArgs..., First>, void> { 
    using typename FuncTypeImpl<N-1, R, Pack<Args...>, Pack<ResultArgs..., First>, void>::Type; 
}; 

template <size_t N, class R, template <class...> class Pack, class... Args, class... ResultArgs> 
struct FuncTypeImpl<N, R, Pack<Args...>, Pack<ResultArgs...>, std::enable_if_t<(N == 0)>> { 
    using Type = std::function<R(ResultArgs...)>; 
}; 

template<std::size_t, typename> 
struct FuncType; 

template<std::size_t N, typename R, typename... A> 
struct FuncType<N, R(A...)> { 
    using Type = typename FuncTypeImpl<N, R, std::tuple<A...>, std::tuple<>, void>::Type; 
}; 

int main() { 
    static_assert(
     std::is_same< 
      FuncType<3, void(int, char, double, int)>::Type, 
      std::function<void(int, char, double)> 
     >::value, 
     "!" 
    ); 
} 

Edit: Ancora un'altra soluzione forse un po 'più semplice (che non ha bisogno di uno std :: tuple):

#include <utility> 
#include <functional> 
#include <type_traits> 

template <class T> 
struct ResultOf; 

template <class R, class... Args> 
struct ResultOf<R(Args...)> { 
    using Type = R; 
}; 

template<std::size_t N, class Foo, class ResultFoo = typename ResultOf<Foo>::Type() , class Voider = void> 
struct FuncType; 

template<std::size_t N, class R, class First, class... Args, class... ResultArgs > 
struct FuncType<N, R(First, Args...), R(ResultArgs...), std::enable_if_t<(N > 0)>>: FuncType<N-1, R(Args...), R(ResultArgs..., First), void> { 
}; 

template<std::size_t N, class R, class First, class... Args, class... ResultArgs > 
struct FuncType<N, R(First, Args...), R(ResultArgs...), std::enable_if_t<(N == 0)>> { 
    using Type = std::function<R(ResultArgs...)>; 
}; 

int main() { 
    static_assert(
     std::is_same< 
      FuncType<3, void(int, char, double*, int)>::Type, 
      std::function<void(int, char, double*)> 
     >::value, 
     "!" 
    ); 
} 
1

Un'altra tuple soluzione basata.

Dovrebbe funzionare anche con C++ 11.

Suppongo che possa essere semplificato ma non so come evitare l'uso del parametro modello bool (sto solo imparando C++ 11).

#include <tuple> 
#include <utility> 
#include <functional> 
#include <type_traits> 


template<std::size_t N, bool Z, typename R, typename...> 
struct FTH1; 

template <typename R, typename... A, typename... B> 
struct FTH1<0U, true, R, std::tuple<A...>, B...> 
{ using type = decltype(std::function<R(A...)>{}); }; 

template <std::size_t N, typename R, typename... A, typename B0, typename... B> 
struct FTH1<N, false, R, std::tuple<A...>, B0, B...> 
{ using type = typename FTH1<N-1U, (N-1U == 0U), R, std::tuple<A..., B0>, B...>::type; }; 


template <std::size_t N, typename> 
struct FuncType; 

template<std::size_t N, typename R, typename... A> 
struct FuncType<N, R(A...)> 
{ using Type = typename FTH1<N, (N == 0), R, std::tuple<>, A...>::type; }; 



int main() { 
    static_assert(
     std::is_same< 
      FuncType<2, void(int, char, double, int)>::Type, 
      std::function<void(int, char)> 
     >::value, 
     "!" 
    ); 
} 

p.s .: scusa per il mio pessimo inglese.

1

Come ho accennato nei commenti, se si pone questo problema, il mio primo approccio sarebbe quello di cambiare il problema stesso! A volte, invece di trovare una "soluzione difficile" per un "problema difficile", è meglio rendere il problema "più semplice"!
Non ci dovrebbe mai essere una necessità, in cui si deve scrivere FuncType<2, R(X,Y,Z)>::type anziché semplice std::function<R(X,Y)>.

Sopra è la mia vera risposta. Per risolvere il tuo problema come delizia del codice, sto mettendo una semplice risposta basata su macro. Invece del tempo di compilazione, ti darà il tipo richiesto in fase di pre-elaborazione.

#define F(R, ...) std::function<R(__VA_ARGS__)> // shorthand for std::function... 
#define FuncType(N, R, ...) FUNC_##N(R, __VA_ARGS__) // Main type 
#define FUNC_0(R, ...) F(R) // overload for 0 arg 
#define FUNC_1(R, _1, ...) F(R, _1) // overload for 1 arg 
#define FUNC_2(R, _1, _2, ...) F(R, _1, _2) // overload for 2 args 
#define FUNC_3(R, _1, _2, _3, ...) F(R, _1, _2, _3) // overload for 3 args 

Usage:

int main() { 
    static_assert(std::is_same< 
    FuncType(2, void, int, char, double, int), // <--- see usage 
    std::function<void(int, char)> 
    >::value, "!"); 
} 

Come potete vedere c'è un piccolo cambiamento nell'uso. Invece di FuncType<2, void(int, char, double, int)>::type, sto usando FuncType(2, void, int, char, double, int). Ecco lo demo.


A partire da ora, i FUNC_N macro sono sovraccarico fino a 3 argomenti. Per ulteriori argomenti, se vogliamo evitare paste di copia, allora possiamo generare un file di intestazione con un semplice programma:

std::ofstream funcN("FUNC_N.h"); // TODO: error check for argv & full path for file 
    for(size_t i = 0, N = stoi(argv[1]); i < N; ++i) 
    { 
    funcN << "#define FUNC_" << i << "(R"; // FUNC_N 
    for(size_t j = 1; j <= i; ++j) 
     funcN << ", _" << j; // picking up required args 
    funcN << ", ...) "; // remaining args 

    funcN << "F(R"; // std::function 
    for(size_t j = 1; j <= i; ++j) 
     funcN << ", _" << j; // passing only relevant args 
    funcN <<")\n"; 
    } 

E semplicemente #include esso:

#define F(R, ...) std::function<R(__VA_ARGS__)> 
#define FuncType(N, R, ...) FUNC_##N(R, __VA_ARGS__) 
#include"FUNC_N.h" 

Ecco la demo per come genera il file di intestazione.

+0

Come già menzionato nei commenti, la necessità è quando si ha 'R (Args ...)', dove non si conosce né quale sia la dimensione del pacchetto di parametri che non sono i tipi effettivi. L'unica cosa che sai è che non li vuoi tutti, tutto qui. :-) ... Comunque, grazie per la risposta. Non molto scalabile come soluzione, ma funziona. – skypjack

+0

@skypjack, '" né quale sia la dimensione del pacchetto di parametri che non sono i tipi effettivi. L'unica cosa che sai è che non li vuoi tutti "". Il mio punto è che potresti voler progettare il tuo progetto in modo tale che tale requisito non si presenti. Tuttavia, non sto giudicando sulla genuinità del tuo bisogno qui. Solo un'opinione La tua soluzione è naturalmente elegante rispetto allo stile macro, poiché non si basa su nient'altro. Tuttavia, la soluzione di cui sopra è anche scalabile. Devi solo creare il file di intestazione usando quel piccolo codice, che coinvolge tutte le macro. – iammilind

Problemi correlati