Newsgroup: it.comp.lang.c++
Titolo: Non ci credo!

#include <new>
#include <iostream>
class T
{
public:
	T() {}
	T(T& t) { if (this == &t) std::cout << "Non ci credo!" << std::endl; }
};
int main()
{
	T t;
	new (&t) T(t);
}
Kiuhnm

Il C++ non inizializza le variabili alla dichiarazione, il cui contenuto 
è per definizione aleatorio... ergo di cosa ti stupisci? :-)
Ma in questo caso credo che il compilatore abbia ottimizzato il tuo 
programmino creando la classe t "di contrabbando" senza stare ad 
aspettare la new di lì a poco :-)

Massimo Soricetti ha scritto:

???
La prima riga crea l'oggetto t chiamando T(), mentre la seconda chiama 
prima operator new(size_t, void *) che restituisce &t e poi chiama il 
costruttore di copia.
Lo stupore nasce dall'aver trovato un modo per far stampare "Non ci credo!".
Da te non funziona?
Kiuhnm
P.S. Lo stupore nasce dal fatto che esiste un modo per far stampare "Non 
ci credo!" a quella classe. Non so se è definito. Di sicuro la 
doppia-costruzione è da evitare, ma il codice è interessante.

 aspettare la new di lì a poco :-)
Nessuna ottimizzazione, ne tanto meno contrabbando.
Il compilatore ha fatto quello che lui gli ha detto.
Ha inizializzato la variabile con il costruttore di default e poi la 
inizializza un'altra volta quando chiama il placement new passandogli 
l'indirizzo di una variabile già inizializzata invece che l'indirizzo di 
memoria 'grezza'.
Il C++ non ti impedisce di scrivere codice insensato.

 ci credo!" a quella classe.
Ce ne è anche un altro.
Max

 Ce ne è anche un altro.
T t = t;
Kiuhnm

 T t = t;
Curioso!
Non sono sicuro che sia valido. Mi rifaccio a Nicola che è più esperto 
di me.
Il mio dubbio è se 't' sia da considerarsi già dichiarato 
nell'initializer list. Sintatticamente l'initializer list è parte del 
declarator. Ma ad un primo sguardo non ho trovato nulla ne in 8. ne in 
8.3 ne in 8.5, che dica esplicitamente in che punto t debba essere 
considerato dichiarato e quindi referenziabile.
Magari mi è sfuggito qualche cosa.

 Magari mi è sfuggito qualche cosa.
Non lo è nessuno dei due.
Kiuhnm

 Non lo è nessuno dei due.
Scusa il quoting. Volevo dire che non è valido né
   T t;
   new (&t) T(t);
né


   T t = t;
Il mio dubbio su questo è se il compilatore debba generare un errore, 
che poi a runtime sia indefinito, sono d'accordo.

 che poi a runtime sia indefinito, sono d'accordo.
Herb Sutter ritiene di no (e non è stato smentito), ma non ho né la 
possibilità né la voglia di spulciare la specificazione del C++.
Kiuhnm

 possibilità né la voglia di spulciare la specificazione del C++.
Tutti i compilatori la permettono, anche il Comeau (che si può provare
online), che è noto come uno dei compilatori più conformi.
Il GCC regiva diversamente alle seguenti due espressioni fino alla versione
3.3:
   T t = t;
   T t(t);
ammettendo la prima, e rifiutando la seconda. Dalla 3.4 entrambe sono
accettate.
Mi pare si possano anche immaginare casi in cui tali espressioni abbiano una
applicazione sensata (oltre a un comportamento definito).
Max

 applicazione sensata (oltre a un comportamento definito).
Mi sono documentato un po' e ora sono quasi convinto che le espressioni 
siano tutte e tre valide.
Nello standard, leggi 3.8(5,6,...).
Kiuhnm

 Nello standard, leggi 3.8(5,6,...).
Visto. Ma per come la capisco io
struct T
{
     T() {}
     T(T& r)
     {
        if (this == &r)
           std::cout << "Adesso ci credo!" << std::endl;
     }
};
int main()
{
     T t;
     new (&t) T(t);
}
è perfettamente valido.
In particolare il codice termina la lifetime dell'oggetto in maniera 
definita (3.8#4) e, lasciando in quello storage un oggetto valido, non 
viola alcuna regola (vedi in particolare 3.8#7 e 3.8#8).
Riguardo ai costrutti:
   T t = t;
e
   T t(t);
però restano aperte alcune questioni.
Non trovo nello standard nulla che dica se il nome 't' debba essere 
considerato dichiarato (e quindi visibile) fin dal momento in cui è 
stato visto il token 't' (quindi i costrutti sono semanticamente validi) 
o solo al termine del declarator (e quindi non lo sono).
Nota che la questione ha rilevanza anche in un altro contesto. Per esempio:
static const int i=5;
void foo()
{
    int i = i;
    // MyCode
}
ovviamente MyCode può accedere a i sia in lettura ce in scrittura 
(ovvero non è costante). Ma qual'è il valore iniziale di i?
Non mi interessa quale sia il comportamente di un dato compilatore. Ma 
quale sia(no) i paragrafi dello standard che lo specifica(no).
Ammettendo che il nome sia accedibile immediatamente, resta comunque da 
verificare se
   T t = t;
e
   T t(t);
siano ben definiti per qualunque T. Questo è diverso dall'esempio del 
placement new.
Consideriamo questo codice:
struct T
{
     T() {}
     T(void* p)
     {
        if (this == p)
           std::cout << "Bene!" << std::endl;
     }
};
int main()
{
     T t(&t);
}
nell'ipotesi suddetta che 't' sia immediatamente visibile.
Il codice è corretto perchè 3.8#5 permette esplicitamente l'utilizzo 
dell'indirizzo come un void*.
Se complichiamo la cosa in questo modo:
struct T
{
     T() {}
     T(const T& r)
     {
        if ( this == &r )
           std::cout << "Bene!" << std::endl;
     }
};
int main()
{
     T t(t);
}
credo di poter dedurre che il codice è ancora definito in quanto nessuna 
delle operazioni "vietate" da 3.8#5 è coinvolta.
Però questo codice:
struct T
{
     int i;
     T() {}
     T(const T& r)
     {
        i = r.i;
     }
};
int main()
{
     T t(t);
}
secondo me non è valido.
Nota che ho fatto apposta a assegnare i nel corpo del costruttore e non 
con la sintassi
     T(const T& r)
       :i(r.i)
     {
     }
Il mio ragionamento è il seguente:
durante l'esecuzione del corpo del costruttore, possiamo considerare 
correttamente costrito this->i. Potremmo quindi pensare che anche r.i 
sia correttamente costruito.
Però siccome il costruttore non è ancora terminato la lifetime di t non 
è ancora iniziata. Pertanto anche se la lifetime di t.i è iniziata, non 
è ancora legale accedere al membro i di r in quanto viola 3.8#5.
Naturalmente dubito che esista un compilatore che possa "incasinare" le 
cose in questo caso. Ma questo è un'altro discorso.
E' possibilissimo che mi sbagli. Quelle fatte sopra sono alcune 
deduzioni delle quali non sono per nulla certo.
Mentre sono certo che non scriverò mai codice di questo genere in un 
programma reale. E se piglio qualcuno che lo fa in un programma a cui 
lavoro gli faccio passare un brutto momento.

 è ancora legale accedere al membro i di r in quanto viola 3.8#5.
Secondo me, nel costruttore, puoi fare con r tutto quello che puoi fare 
con this (e niente di più). Del resto this ed r sono la stessa identica 
cosa. La sezione 3.8 fa un discorso generale, mentre nel nostro caso ci 
troviamo all'interno di un costruttore, funzione alquanto particolare.
Comunque lo standard avrebbe potuto citare questi casi esplicitamente. 
Probabilmente non ci hanno pensato.
Kiuhnm

 Probabilmente non ci hanno pensato.
Forse.

 dire il grep) necessario!
Premetto che oggi sono stanco e potrei dire una cavolata.
Se
int x = 12;
{ int x = x; }
non è indefinito (è solo il valore di x ad essere 
indefinito/indeterminato) allora, almeno per coerenza, non lo dovrebbe 
essere neanche
T x = 12;
{ T x = x; }
Lo standard dice "Here the second x is initialized with its own 
(indeterminate) value". Questo significa che se il nostro costruttore di 
copia di T eseguisse una semplice copia bit a bit, dovremmo ottenere lo 
stesso comportamento che si ottiene con int.
Kiuhnm

 stesso comportamento che si ottiene con int.
Si. E infatti questo vale se la classe usa il costruttore di default e 
si comporta come POD (ovvero se anche tutti i suoi membri e classi base 
sono essi stessi POD).
Ma le regole sono diverse se la classe ha 
costruttori/distruttori/copiatori definiti dall'utente.

 costruttori/distruttori/copiatori definiti dall'utente.
Questo non è coerente. Se l'utente scrive un costruttore identico a 
quello di default, non ci devono essere differenze.
E comunque il 3.3.1 non vale solo per i tipi built-in, ma per qualsiasi 
tipo.
Kiuhnm

Kiuhnm ha scritto:
[...]
Per curiosità, ho provato a chiedere cosa ne pensano i frequentatori di 
clc++.
Kiuhnm