concatenamento ostream, ordine di uscita

Ho una funzione che accetta un riferimento ostream come argomento, scrive alcuni dati nello stream e quindi restituisce un riferimento allo stesso stream, in questo modo:

 #include  std::ostream& print( std::ostream& os ) { os << " How are you?" << std::endl; return os; } int main() { std::cout << "Hello, world!" << print( std::cout ) << std::endl; } 

L’output di questo codice è:

  How are you? Hello, world!0x601288 

Tuttavia, se separo le espressioni concatenate in due dichiarazioni, come questa

 int main() { std::cout << "Hello, world!"; std::cout << print( std::cout ) << std::endl; } 

quindi almeno ottengo l’ordine corretto nell’output, ma otteniamo comunque un valore esadecimale:

 Hello, world! How are you? 0x600ec8 

Mi piacerebbe capire cosa sta succedendo qui. Una funzione normale ha la precedenza operator<< , ed è per questo che l’ordine di uscita si inverte? Qual è il modo corretto di scrivere una funzione che inserisce dati in un ostream ma che può anche essere concatenata con l’ operator<< ?

Il comportamento del tuo codice non è specificato secondo lo standard C ++.

Spiegazione

Il seguente (Ho rimosso std::endl per semplicità)

 std::cout << "Hello, world!" << print( std::cout ); 

è equivalente a questo:

 std::operator(operator<<(std::cout, "Hello, World!"), print(std::cout)); 

che è una chiamata di funzione, passando due argomenti:

  • Il primo argomento è: operator<<(std::cout, "Hello, World!")
  • Il secondo argomento è: print(std::cout)

Ora, lo standard non specifica l'ordine in cui vengono valutati gli argomenti. Non è specificato Ma il tuo compilatore sembra valutare prima il secondo argomento, ecco perché stampa "Come stai?" prima, valutando il secondo argomento con un valore di tipo std::ostream& che poi viene passato alla chiamata mostrata sopra (quel valore è l'object std::cout stesso).

Perché l'output esadecimale?

Si ottiene l'output esadecimale perché il secondo argomento restituisce std::cout , che viene stampato come numero esadecimale, poiché std::cout convertito implicitamente nel valore puntatore del tipo void* , motivo per cui viene stampato come numero esadecimale.

Prova questo:

 void const *pointer = std::cout; //implicitly converts into pointer type! std::cout << std::cout << std::endl; std::cout << pointer << std::endl; 

Stamperà lo stesso valore per entrambi. Ad esempio, questo esempio su ideone stampa questo:

 0x804a044 0x804a044 

Si noti inoltre che non ho usato il cast esplicito ; piuttosto std::cout viene convertito implicitamente in tipo puntatore.

Spero possa aiutare.


Qual è il modo corretto di scrivere una funzione che inserisce dati in un ostream ma che può anche essere concatenata con l'operatore <

Quando dipende da cosa intendi concatenando? Ovviamente, quanto segue non funzionerebbe (come spiegato sopra):

 std::cout << X << print(std::cout) << Y << Z; //unspecified behaviour! 

Non importa come scrivi print() .

Tuttavia questo è ben definito:

 print(std::cout) << X << Y << Z; //well-defined behaviour! 

Il motivo è che la funzione print () verrà valutata prima del resto dell’istruzione e restituirà un riferimento a cout che verrà quindi stampato come puntatore (cout << cout). Questo ordine di valutazione è in realtà un comportamento non specificato, ma sembra essere il caso del tuo compilatore.

Per quanto riguarda la definizione di una “funzione” sensibile al stream che in realtà ha definito un comportamento con la stessa funzionalità, ciò funzionerebbe;

 #include  template  std::basic_ostream& print ( std::basic_ostream& os ) { os << " How are you?" << std::endl; return os; } int main() { std::cout << "Hello, world!" << print << std::endl; } 

Vedi anche questa risposta per un po 'più di dettaglio su cosa significa "non specificato" in questo caso.

Nella tua dichiarazione std::cout << "Hello, world!" << print( std::cout ) << std::endl std::cout << "Hello, world!" << print( std::cout ) << std::endl non è definito se std::cout << "Hello, world!" succede prima o dopo la print( std::cout ) . Ecco perché l'ordine potrebbe non essere quello che ti aspetti.

Il valore esadecimale deriva dal fatto che stai anche facendo std::cout << std::cout ( print restituisce std::cout che viene inserito nella catena << ). La mano destra std::cout viene convertita in un void * e viene stampato sull'output.

Funzionerebbe, per combinare la print con << e controllare l'ordine:

 print( std::cout << "Hello, world!" ) << std::endl; 

Oppure, se vuoi una funzione chiamata con << , vedi la risposta di Joachim.

Uscita esadecimale

Prima di C ++ 11, la class std::ostream ha una funzione di conversione a void* . Dato che la funzione di print restituisce std::ostream& , quando si valuta std::cout << print(...) , il valore restituito std::ostream lvalue verrà convertito implicitamente in void* e quindi verrà emesso come valore del puntatore. Questo è il motivo per cui esiste un output esadecimale.

Dal momento che C ++ 11, questa funzione di conversione è sostituita da una funzione di conversione esplicita in bool , quindi provare a generare un object std::ostream diventa mal formato.

Ordine di valutazione

Prima di C ++ 17, l'operatore sovraccarico è considerato una chiamata di funzione per l'analisi dell'ordine di valutazione e l'ordine di valutazione di diversi argomenti di una chiamata di funzione non è specificato. Quindi non è strano che la funzione di print sia valutata in primo luogo, il che causa How are you? è emesso in primo luogo.

Dal C ++ 17, l'ordine di valutazione degli operandi dell'operatore << è rigorosamente da sinistra a destra, e gli operandi dell'operatore sovraccarico condividono lo stesso ordine di valutazione di quelli del bulit-in uno (vedere maggiori dettagli qui ). Quindi il tuo programma otterrà sempre l'output (supponiamo che la print restituisca qualcosa che può essere emesso)

 Hello, world! How are you? something returned by print 

ESEMPIO LIVE