2010-05-24 2 views
7

quindi ho una serie di funzioni globali, dicono:Come si può creare una funzione "passthru" in C++ usando macro o metaprogrammazione?

foo_f1(int a, int b, char *c); 
foo_f2(int a); 
foo_f3(char *a); 

Voglio fare un C++ wrapper per questi, qualcosa come:

MyFoo::f1(int a, int b, char* c); 
MyFoo::f2(int a); 
MyFoo::f3(char* a); 

ci sono circa 40 funzioni come queste, 35 di loro Voglio solo passare alla funzione globale, gli altri 5 con cui voglio fare qualcosa di diverso.

Idealmente l'attuazione di MyFoo.cpp sarebbe qualcosa di simile:

PASSTHRU(f1, (int a, int b, char *c)); 
PASSTHRU(f2, (int a)); 

MyFoo::f3(char *a) 
{ 
    //do my own thing here 
} 

ma sto avendo difficoltà a capire un modo elegante per rendere la macro PASSTHRU sopra.

Quello che ho veramente bisogno è qualcosa di simile alle getArgs X mitici() di seguito:

MyFoo::f1(int a, int b, char *c) 
{ 
    X args = getArgs(); 
    args++; //skip past implicit this.. 
    ::f1(args); //pass args to global function 
} 

Ma a corto di cadere in assemblea non riesco a trovare una buona implementazione di getArgs().

+1

Per la maggior parte, questo è impossibile con la versione corrente di C++. Se Google per "l'inoltro perfetto" dovresti ottenere molti successi sul perché ora non funziona, e cosa aggiunge C++ 0x per risolvere il problema. –

+3

Quale scopo è fare della funzione globale un membro di una classe, specialmente quando si tenta di saltare "questo" in ogni caso? – GManNickG

risposta

10

Si potrebbe utilizzare Boost.Preprocessor di lasciare che il seguente:

struct X { 
    PASSTHRU(foo, void, (int)(char)) 
}; 

... espandere a:

struct X { 
    void foo (int arg0 , char arg1) { return ::foo (arg0 , arg1); } 
}; 

... usando queste macro :

#define DO_MAKE_ARGS(r, data, i, type) \ 
    BOOST_PP_COMMA_IF(i) type arg##i 

#define PASSTHRU(name, ret, args) \ 
    ret name (\ 
    BOOST_PP_SEQ_FOR_EACH_I(DO_MAKE_ARGS, _, args) \ 
) { \ 
    return ::name (\ 
     BOOST_PP_ENUM_PARAMS(BOOST_PP_SEQ_SIZE(args), arg) \ 
    ); \ 
    } 
+0

dannazione, bastonatemi; anche più elegante. Il tuo ha comunque un bug;) ~ –

+0

Questo porta alla ricorsione infinita. :) – GManNickG

+0

@GMan: Oops, grazie. –

0

Il mio pensiero iniziale, e questo probabilmente non funzionerà o altri lo avrebbero affermato, è mettere tutte le funzioni di base in una classe come virtuali. Quindi, scrivere i miglioramenti delle funzionalità in classi ereditate ed eseguirle. Non è un wrapper macro, ma puoi sempre chiamare le funzioni globali nelle classi virtuali.

Con alcuni trucchi di assemblaggio, probabilmente si potrebbe fare esattamente ciò che si vorrebbe, ma si perderebbe la portabilità più che probabile. Interessante domanda e voglio anche sentire le altre risposte.

0

È possibile utilizzare un namespace se non si desidera trattare argomenti di classe, ad esempio this. Puoi anche usare i metodi membro static in una classe, ma penso che alle persone non piaccia più.

#ifndef __cplusplus 
#define PASSTHRU(type, prefix, func, args) type prefix##_##func args 
#else 
#define PASSTHRU(type, prefix, func, args) type prefix::func args 
#endif 

O

#ifndef __cplusplus 
#define PASSTHRU(type, prefix, func, ...) type prefix##_##func(__VA_ARGS__) 
... 
2

sintassi leggermente diversa ma ...

#include <boost/preprocessor.hpp> 
#include <iostream> 

void f1(int x, int y, char* z) { std::cout << "::f1(int,int,char*)\n"; } 

#define GENERATE_ARG(z,n,unused) BOOST_PP_CAT(arg,n) 
#define GET_ARGS(n) BOOST_PP_ENUM(n, GENERATE_ARG, ~) 

#define GENERATE_PARAM(z,n,seq) BOOST_PP_SEQ_ELEM(n,seq) GENERATE_ARG(z,n,~) 

#define GENERATE_PARAMS(seq) BOOST_PP_ENUM(BOOST_PP_SEQ_SIZE(seq), GENERATE_PARAM, seq) 

#define PASSTHROUGH(Classname, Function, ArgTypeSeq) \ 
    void Classname::Function(GENERATE_PARAMS(ArgTypeSeq)) \ 
{ \ 
    ::Function(GET_ARGS(BOOST_PP_SEQ_SIZE(ArgTypeSeq))); \ 
} 

struct test 
{ 
    void f1(int,int,char*); 
}; 

PASSTHROUGH(test,f1,(int)(int)(char*)) 

int main() 
{ 
    test().f1(5,5,0); 

    std::cin.get(); 
} 

Si potrebbe ottenere qualcosa di più vicino al tuo, se si utilizza tuple, ma che avrebbe dovuto fornire il conteggio arg alla funzione di base (è possibile 't ottenere una dimensione da una tupla). Un modo del genere:

PASSTHROUGH(test,f1,3,(int,int,char*)) 

Che cosa stai cercando? Sapevo che si poteva fare; ho impiegato circa mezz'ora per risolvere. Sembri aspettarti che ci sia un implicito "questo" che deve essere eliminato ma non vedo perché ...quindi forse ho frainteso il problema. In ogni caso, questo ti consentirà di impostare rapidamente le funzioni membro "passthrough" predefinite che si riferiscono a una funzione globale. Avrai bisogno di un DECPASSTHROUGH per la dichiarazione della classe se vuoi saltare di doverli dichiarare tutti ... o potresti modificarlo per fare funzioni inline.

Suggerimento: utilizzare BOOST_PP_STRINGIZE ((XX)) per verificare l'output dei metafunzioni del preprocessore.

+0

Il 'questo' sarebbe un problema solo se si accede direttamente alla memoria. Grazie per la soluzione, speriamo che la gente qui vada bene includendo il preprocessore Boost (non stiamo usando Boost, ma il PP sembra indipendente). – Ryan

+3

Beh, se le persone non sono d'accordo con l'uso di boost puoi semplicemente fare quello che fanno gli altri sviluppatori di C++ quando non possono usare boost: prova a scrivere quelle parti di cui hai veramente bisogno, fallo male e lamentati incompetenza dei tuoi capi. –

+0

@Ryan: PP è autonomo e funziona anche in C. In alternativa puoi sempre estrarre i componenti Boost con lo strumento [bcp] (http://www.boost.org/tools/bcp/index.html). –

0

L'inoltro perfetto si basa sui riferimenti di valore. STL ha un post di blog su di esso a http://blogs.msdn.com/b/vcblog/archive/2009/02/03/rvalue-references-c-0x-features-in-vc10-part-2.aspx e si vorrebbe scegliere un compilatore che supporta la funzionalità per adottare questo approccio. Sta discutendo di Visual C++ 2010.

+0

L'OP non ha realmente bisogno di un inoltro perfetto poiché fornisce informazioni sufficienti per inoltrare i tipi corretti. L'inoltro perfetto è richiesto quando lavori a un livello più generico. Data l'una o l'altra delle macro implementazioni spiegate nei commenti, l'OP potrebbe inserire int & o int const e come si applicano e verrà inoltrato esattamente come specificato per l'eventuale funzione di implementazione. –

5

A 40 funzioni dispari, è possibile digitare i wrapper a mano in un'ora. Il compilatore verificherà la correttezza del risultato. Assumi 2 minuti in più per ogni nuova funzione che deve essere completata e 1 minuto in più per un cambio di firma.

Come specificato, e senza menzione di aggiornamenti o modifiche frequenti, non sembra che questo problema richieda una soluzione astuta.

Quindi, la mia raccomandazione è di mantenerlo semplice: farlo a mano. Copia i prototipi nel file sorgente, quindi utilizza le macro della tastiera (emacs/Visual Studio/vim) per sistemare le cose e/o più passaggi di ricerca e sostituzione, generando un set di definizioni e un set di dichiarazioni. Taglia dichiarazioni, incolla nell'intestazione. Inserisci le definizioni per le funzioni non passanti. Questo non ti vincerà nessun premio, ma finirà presto.

Nessuna dipendenza extra, nessun nuovo strumento di compilazione, funziona bene con il browsing del codice/tag/intellisense/etc., Funziona bene con qualsiasi debugger e nessuna sintassi specializzata/caratteristiche moderne/modelli/ecc., Così chiunque può capire il risultato. (È vero che nessuno rimarrà impressionato, ma sarà un buon tipo di imperturbabile.)