2010-09-06 11 views
6

Diciamo che avete un po 'di classe bersaglio con alcuni metodi su di esso:valore di ritorno vuoto da una funzione utilizzata come input per una funzione template è visto come un parametro di

class Subject 
{ 
public: 
    void voidReturn() { std::cout<<__FUNCTION__<<std::endl; } 
    int intReturn() { std::cout<<__FUNCTION__<<std::endl; return 137; } 
}; 

E una classe di valore (simile nel concetto a Boost .Any):

struct Value 
{ 
    Value() {} 
    Value(Value const & orig) {} 
    template< typename T > Value(T const & val) {} 
}; 

E voglio produrre un oggetto di valore utilizzando un metodo dalla classe oggetto:

Subject subject; 
Value intval(subject.intReturn()); 
Value voidVal(subject.voidReturn()); // compilation error 

ottengo i seguenti errori in VC++ 2008:

error C2664: 'Value::Value(const Value &)' : cannot convert parameter 1 from 'void' to 'const Value &' 
Expressions of type void cannot be converted to other types 

e gcc 4.4.3:

/c/sandbox/dev/play/voidreturn/vr.cpp:67: error: invalid use of void expression 

Il contesto di questo è quando si desidera utilizzarlo all'interno di una classe template:

template< typename Host, typename Signature > class Method; 

// Specialization for signatures with no parameters 
template< typename Host, typename Return > 
class Method< Host, Return() > 
{ 
public: 
    typedef Return (Host::*MethodType)(); 
    Method(Host * host, MethodType method) : m_Host(host), m_Method(method) {} 

    Value operator()() { return Value((m_Host->*m_Method)()); } 
private: 
    Host  * m_Host; 
    MethodType m_Method; 
}; 

Utilizzando questa classe Method sul metodo che restituisce qualcosa (vale a dire intReturn) sarebbe simile:

Method< Subject, int() > intMeth(&subject, &Subject::intReturn); 
Value intValue = intMeth(); 

Tuttavia, facendo questo con il metodo voidReturn:

Method< Subject, void() > voidMeth(&subject, &Subject::voidReturn); 
Value voidValue = voidMeth(); 

cede errori simili come sopra.

Una soluzione è quella di specializzarsi ulteriormente parzialmente Metodo per i tipi di ritorno void:

template< typename Host > 
class Method< Host, void() > 
{ 
public: 
    typedef void Return; 
    typedef Return (Host::*MethodType)(); 
    Method(Host * host, MethodType method) : m_Host(host), m_Method(method) {} 

    Value operator()() { return (m_Host->*m_Method)(), Value(); } 
private: 
    Host  * m_Host; 
    MethodType m_Method; 
}; 

Oltre esso solo sentirsi brutta, sto anche volendo specializzarsi classe Metodo per numero X di parametri di firma, che coinvolge già un sacco di duplicazione del codice (speranzoso Boost.Preprocessor può aiutare qui), e quindi l'aggiunta di una specializzazione per i tipi di restituzione vuoti raddoppia solo lo sforzo di duplicazione.

Esiste comunque la possibilità di evitare questa seconda specializzazione per i tipi di resi void?

+1

+1. Questa è una domanda molto ben scritta, penso. –

+0

Assolutamente. I second that – Chubsdad

risposta

4

È possibile utilizzare Return e solo specializzarsi nella gestione di operator(). Non è necessario duplicare l'intero modello.

// I think it's a shame if c++0x really gets rid of std::identity. It's soo useful! 
template<typename> struct t2t { }; 

// Specialization for signatures with no parameters 
template< typename Host, typename Return > 
class Method< Host, Return() > 
{ 
public: 
    typedef Return (Host::*MethodType)(); 
    Method(Host * host, MethodType method) : m_Host(host), m_Method(method) {} 

    Value operator()() { return call(t2t<Return>()); } 

private: 
    Value call(t2t<void>) { return Value(); } 

    template<typename T> 
    Value call(t2t<T>) { return Value((m_Host->*m_Method)()); } 

private: 
    Host  * m_Host; 
    MethodType m_Method; 
}; 
+0

... e la ragione per sbarazzarsi di 'identity' è che definendo il suo' operator() 'per usare riferimenti rvalue invece di' const & 'romperebbe troppo codice installato! – Potatoswatter

+0

@Potatoswatter too bad :( –

+0

Sì, questo è quello che stavo cercando! Ricordo di aver letto su Type2Type qualche tempo fa da Alexandrescu, ma da allora mi sono dimenticato. – Chris

2

No, non è assolutamente possibile passare uno void. È un'irregolarità nella lingua.

L'elenco degli argomenti delle funzioni (void) viene convertito come (). Bjarne preferisce il secondo al primo e, a malincuore, ha concesso alla convenzione C un tipo molto limitato di zucchero sintattico. Non è nemmeno possibile sostituire un alias typedef di void e certamente non si possono avere altri argomenti.

Personalmente ritengo che questa sia una cattiva idea. Se è possibile scrivere void(expr), dovrebbe essere possibile "inizializzare" un argomento anonimo di tipo void. Se si potesse anche scrivere una funzione con un numero arbitrario di argomenti void, ci sarebbe un modo per eseguire un numero di espressioni in un ordine non specificato, che esprimerebbe la concorrenza in un modo.

Come per la gestione di elenchi di argomenti di dimensioni diverse (noto anche come variadic), vedere modelli variadici in C++ 0x prima di iniziare a provare a imparare il preprocessore di Boost.

+0

Grazie, ne avevo paura. Pensavo di aver visto qualcosa in boost.function dove stavano facendo qualcosa di simile ma apparentemente no. E grazie per il suggerimento sui modelli variadic, sfortunatamente non ho aggiornato i miei compilatori (ancora). – Chris

+0

"Un elenco di argomenti della funzione con un argomento di tipo void viene modificato per essere un elenco senza argomenti." -> in realtà è ancora più severo. È in questo modo in C99, ma in C++ la sostituzione è puramente sintattica: la sequenza di token "(void)" indica un elenco di parametri zero. Ma per esempio "(Void)" dove "Void" denota il tipo di vuoto è illegale in C++. –

+0

@Johannes: corretto. – Potatoswatter

Problemi correlati