2013-05-04 15 views
5

Voglio passare un parametro (s) (di un certo tipo concreto, ad esempio int) alla funzione membro per il riferimento r- o l- value (const). La mia soluzione è:rvalue or lvalue (const) parametro di riferimento

#include <type_traits> 
#include <utility> 

struct F 
{ 
    using desired_parameter_type = int; 

    template< typename X, typename = typename std::enable_if< std::is_same< typename std::decay<X>::type, desired_parameter_type >::value >::type > 
    void operator() (X && x) const 
    { 
     // or even static_assert(std::is_same< typename std::decay<X>::type, desired_parameter_type >::value, ""); 
     std::forward<X>(x); // something useful 
    } 
}; 

Un'altra exaple è qui http://pastebin.com/9kgHmsVC.

Ma è troppo prolisso. Come farlo in un modo più semplice?

Forse dovrei usare la sovrapposizione di std::remove_reference e std::remove_const invece di std::decay, ma qui c'è solo una semplificazione.

+0

Non è necessario passare un tipo semplice come 'int' di valore. Nel caso più generale, credo che C++ 11 abbia un caso speciale per questo, in cui un parametro rvalue verrà letto come un parametro rvalue * o * lvalue. Non riesco a ricordare la situazione esatta in cui ciò si applica però. – Dave

+0

Quindi si desidera che 'x' sia un riferimento di rvalue (se viene passato un riferimento di rval) o un riferimento di lvalue a' const'? Sto capendo correttamente, che 'X &&' non sarebbe accettabile per te perché sarebbe un riferimento lvalue a non-'const' quando viene passato un lvalue? –

+0

Ah, eccoci qui, credo che stai provando a farlo: http://thbecker.net/articles/rvalue_references/section_07.html che è spiegato usando i rvalues ​​nella prossima pagina. Urla se ho frainteso la domanda. – Dave

risposta

4

Se ho compreso correttamente la tua domanda, desideri avere una singola funzione il cui parametro sia un riferimento di rvalue (nel caso sia fornito un rvalore) o un riferimento di lvalue a const (nel caso sia fornito un lvalue).

Ma quale sarebbe questa funzione? Bene, dato che deve essere in grado di gestire entrambi i casi, incluso il caso in cui è fornito un lvalue, non può modificare il suo input (almeno non quello legato al parametro x) - se lo facesse, violerebbe la semantica del const riferimento.

Ma poi di nuovo, se non può modificare lo stato del parametro, non vi è alcun motivo per consentire un riferimento di rvalue: piuttosto lasciare che sia un riferimento di lvalue a const per tutto il tempo. I riferimenti di lvalue a const possono essere associati a rvalues, quindi sarà possibile passare sia rvalues ​​che lvalue.

Se la semantica della funzione è diversa basano su ciò che viene passato, quindi direi più senso per scrivere due tali funzioni: una che richiede un riferimento rvalue e uno che richiede un riferimento Ivalue a const.

+0

constness è facoltativo (quindi tutto di '&&', '&', 'const &' allowed) – Orient

+1

@Dukales: Non capisco :(Cosa intendi? Se la funzione deve essere in grado di lavorare con 'const' oggetti, significa che non proverà a modificare 'x'. Ma di nuovo, questo significa che non hai bisogno di un riferimento di rval –

+0

Conosci metaprogramming?Supponiamo che le funzioni a cui sono passati i parametri determineranno cosa fare con (come inviare) la loro particolare "referenza" e costanza. – Orient

1

Come ha detto Andy, la cosa importante qui è ciò che potreste effettivamente fare do all'interno della vostra funzione che avrebbe senso.

  • È possibile inoltrare argomenti a un'altra funzione. In questo caso, l'uso di un modello non ha importanza, perché se viene fornito il tipo di parametro sbagliato verrà comunque generato un errore di compilazione (tipi errati inviati alla seconda funzione). È possibile utilizzare template <typename T> blah (T && x).
  • Qualsiasi altra cosa richiederebbe di scrivere codice diverso a seconda che si tratti di un riferimento di valore r- o l, quindi sarà necessario scrivere 2 funzioni in ogni caso: blah (const int & x) e blah (int && x).

Presumo che si stia tentando la prima opzione e si sta tentando di rendere più semplici i possibili errori del compilatore. Bene, direi che non ne vale la pena; il programmatore vedrà comunque una lista "chiamata da ..." nell'output di qualsiasi compilatore decente.

+0

Sotto la funzione può significare un operatore con una lista di inizializzazione massiva. – Orient

+0

@Dukales: Ma è solo l'inoltro, quindi puoi usare i modelli e ottenere ugualmente errori del compilatore se vengono chiamati erroneamente. – Dave

1

In realtà, questa è una buona domanda. Finora ho anche usato il trucco di riferimento universale più il martello enable_if. Qui presento una soluzione che non fa uso di modelli e utilizza cast di lvalue come alternativa.

Quello che segue è un esempio reale in cui la situazione si verifica con l'esempio noto di inplace ofstream utilizzo che non è possibile (o molto dura) in C++ 98 (uso ostringstream nell'esempio di rendere più chiaro).

Per prima cosa verrà visualizzata una funzione sul riferimento lvalue come spesso si vede in C++ 98.

#include<iostream> 
#include<sstream> 
struct A{int impl_;}; 

std::ostringstream& operator<<(std::ostringstream& oss, A const& a){ 
    oss << "A(" << a.impl_ << ")"; // possibly much longer code. 
    return oss; 
} 
// naive C++11 rvalue overload without using templates 
std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){ 
    oss << "A(" << a.impl_ << ")"; // ok, but there is code repetition. 
    return oss; 
} 

int main() { 

    A a{2}; 
    {// C++98 way 
     std::ostringstream oss; 
     oss << a; 
     std::cout << oss.str() << std::endl; // prints "A(2)", ok" 
    } 
    {// possible with C++11, because of the rvalue overload 
     std::cout << (std::ostringstream() << a).str() << std::endl; //prints "A(2)", ok 
    } 
} 

Come si può vedere in C++ 11 possiamo ottenere ciò che non possiamo in C++ 98. Vale a dire l'uso dello ostringstream (o ofstream) sul posto. Ora arriva la domanda OP, i due sovraccarichi sembrano molto simili, possono entrambi essere uniti in uno?

Un'opzione è utilizzare il riferimento universale (Ostream&&) e facoltativamente con enable_if per vincolare il tipo. Non molto elegante.

Ciò che ho trovato utilizzando questo esempio di "mondo reale" è che se si desidera utilizzare lo stesso codice per ref lvalue e ref è perché probabilmente è possibile convertirne uno all'altro!

std::ostringstream& operator<<(std::ostringstream&& oss, A const& a){ 
    return operator<<(oss, a); 
} 

questo appare come una funzione infinitamente ricorsiva, ma non lo è perché oss è un riferimento lvalue (sì, è un riferimento lvalue perché ha un nome). Così chiamerà l'altro sovraccarico.

È ancora necessario scrivere due funzioni, ma una ha un codice che non è necessario mantenere.

In sintesi, se "ha senso" © applicare una funzione sia a un riferimento (non const) lvalue che a un valore rvalore che significa anche che è possibile convertire il rvalue in un valore lvalue e quindi inoltrare a una singola funzione. Nota che "ha senso" dipende dal contesto e dal significato del codice previsto, ed è qualcosa che dobbiamo "dire" al compilatore chiamando esplicitamente il sovraccarico di lvalue.

Non sto dicendo che questo è meglio che usare un riferimento universale, dico che è un'alternativa e probabilmente l'intenzione è più chiara.

Codice modificabile qui: http://ideone.com/XSxsvY. (Il feedback è benvenuto)

Problemi correlati