Quantcast
Channel: User Peter - Reinstate Monica - Stack Overflow
Viewing all articles
Browse latest Browse all 223

Answer by Peter - Reinstate Monica for I don't understand why the compiler behaves this way

$
0
0

The case is a bit tricky. Generally, your cast to "reference to Profesor" is a cast to an unrelated type. While you can do that, it is an error, and the result is undefined. The reason this is allowed at all is that the compiler, in general, may not be able to tell whether the types are indeed unrelated:

File 1:

struct S { int i; } s { 1 };

File 2:

struct S; // note: S's definition is unknown hereint f(S *ps) { int* pi = reinterpret_cast<int*>(sp); return *pi; }

The above is legitimate (although brittle and C-style) code. The compiler must trust the coder about the reinterpret cast.

In your case though, the classes are indeed unrelated, and the program is ill-formed.

Something interesting happens when you change f() to take a const reference (thanks to 273K for the idea). Such a reference can bind to a temporary which will continue to exist as long as the const reference exists. That temporary can be constructed via the conversion function so that the program becomes valid. (Using f() s return value is also legit, even though the temporary has expired, because it is a true copy.)

I provided both functions and instrumented your code so that each function makes an output. I had to make Persoana::varsa mutable in order to change it through a const ref, which is fine; the object is not in read-only memory.

Note in the output how the conversion function is invoked for the const reference but not for the regular reference.

I also used the macro offsetof() to print the relative positions of the data members: Profesor's marca resides at the same offset as Persoana's varsa, which is the reason it is changed by the function which is tricked into thinking it's handling a Persoana.

Of course, the entire code handling the flawed reference conversion is ill-formed and might as well crash. But since the classes are simple data structures, and since Persoana is smaller than Profesor, the bad memory access "works" here.

Last not least let me remark that in a typical beginner OO lesson, a Profesor would likely be a special person and as such inherit from Persoana instead of re-defining data members like varsa.

Here is the demonstration program:

#include <iostream>#include <cstddef>using namespace std;class Persoana {public:    mutable int varsta;    Persoana(int v = 30) : varsta(v) {}};class Profesor {public:    int marca = 100;    int varsta;    Profesor(int v = 20) : varsta(v) {}    operator Persoana() {        Persoana p;        p.varsta = varsta;        cout << "operator Persoana(): varsta == " << p.varsta << endl;        return p;    }};Persoana f(const Persoana& p) {    p.varsta++;    cout << "f(const Persoana& p): varsta incremented to " << p.varsta << endl;    return p;}Persoana f(Persoana& p) {    p.varsta++;    cout << "f(Persoana& p) with NON-CONST argument: \"varsta\" incremented to " << p.varsta << endl;    return p;}int main() {    cout << "positions of members in the classes:\n"<< "Persoana::varsta: " << offsetof(Persoana, varsta) << endl<< "Profesor::marca: " << offsetof(Profesor, marca) << endl<< "Profesor::varsta: " << offsetof(Profesor, varsta) << endl << endl;    Profesor prof;    cout << " -------- Test for f((const Persoana&)prof) ----------\n";    cout << "varsta of prof: " << prof.varsta << endl;    //----------------------------------------------------------------------    {        // The conversion function Profesor::Persoana()         // will be invoked, producing a true        // (though temporary) `Persoana` object. The const reference        // will bind to that `Persoana` temporary object.        // The user-defined conversion function is called implicitly:        // No cast necessary!        const Persoana& const_persoana_ref = prof;         cout << "varsta of temporary Persoana returned from f(const): " << f(const_persoana_ref).varsta << endl;    }    // equally valid explicit conversion demonstration    {        const Persoana& const_persoana_ref2 = static_cast<const Persoana&>(prof);    }    cout << "varsta of prof: " << prof.varsta << endl;    cout << "marca of prof: " << prof.marca << endl;    cout << endl;    //----------------------------------------------------------------------    // This does not compile:    // Persoana& persoana_ref = prof;    // Neither does this:    // Persoana& persoana_ref = static_cast<Persoana &>(prof);    // We must force the compiler to change the reference type     // with a  reinterpret_cast:    Persoana& persoana_ref = reinterpret_cast<Persoana&>(prof);    cout << " -------- Test for f((Persoana&)) (with non-const argument)\n";    cout << "varsta of prof: " << prof.varsta << endl;    // NO conversion Profesor::Persoana() will be invoked for f() argument;     // instead the compiler is tricked into thinking that f() gets a ref to Persoana    // while the object is actually a Profesor. Consequently, when the code accesses    // the int at offset 0, it changes the Profesor's marca, not varsa (varsa    // is likely at offset 4 on a 32 bit system).    cout << "varsta of Persoana returned from f(non-const): " << f(persoana_ref).varsta << endl;    cout << "varsta of prof: " << prof.varsta << endl;  // unchanged!    cout << "marca of prof: " << prof.marca << endl;    // oops: changed!}

Viewing all articles
Browse latest Browse all 223

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>