Difference between static_cast<const A>(*this) and static_cast<const A&>(*this)

J

junyangzou

This is an excerpt from "Effective C++" item 03. I add some code to actually make it runnable. And here is the question: I tried to change the static_cast<const TextBook&> to static_cast<const TextBook>, and it runs good. So is there any difference between the two statement.


1 #include <iostream>
2 #include <string>
3
4 using namespace std;
5
6 class TextBook {
7 public:
8 TextBook( std::string name = "" )
9 :text(name){}
10 const char& operator[] ( std::size_t position ) const {
11 return text[position];
12 }
13
14 char& operator[] ( std::size_t position ) {
15 return const_cast<char&>(
16 static_cast<const TextBook&>(*this)[position]
17 );
18 }
19 private:
20 std::string text;
21 };
22
23 int main() {
24 TextBook book("Effective C++");
25 const TextBook book2(book);
26
27 //cout << book[0] << " " << book2[0] << endl;
28 book[0] = 'D';
29 cout << book[0] << endl;
30 return 0;
31 }
 
J

James Kanze

This is an excerpt from "Effective C++" item 03. I add some
code to actually make it runnable. And here is the question:
I tried to change the static_cast<const TextBook&> to
static_cast<const TextBook>, and it runs good. So is there any
difference between the two statement.

static_cast<TextBook const&> obtains a reference to the object;
you're still dealing with the original object.
static_cast<TextBook> makes a copy of the original object; your
dealing with a temporary copy, which will be destructed at the
end of the full expression. And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It
may seem to work, in simple cases, but you can't count on it.
 
J

junyangzou

static_cast<TextBook const&> obtains a reference to the object;

you're still dealing with the original object.

static_cast<TextBook> makes a copy of the original object; your

dealing with a temporary copy, which will be destructed at the

end of the full expression. And the since the function where

you do the operation ends up returning a reference to data

inside this temporary, you end up with undefined behavior. It

may seem to work, in simple cases, but you can't count on it.
I am using g++. And if the operation returns a reference to data inside the temporary. It shouldn't affect the original object book in any way. But the result is
The object changed!
 
V

Victor Bazarov

[...] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It .. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
may seem to work, in simple cases, but you can't count on it.
I am using g++. And if the operation returns a reference to data inside the temporary. It shouldn't affect the original object book in any way. But the result is
The object changed!

The behavior is *undefined*. Anything could happen, and among valid
outcomes can be that the original object changes. An explanation to it
might be that the compiler guesses what you intended to do and changes
the original object instead, but that's still *undefined* and you should
not rely on it. Read up on undefined behavior.

V
 
J

junyangzou

[...] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It

. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I am using g++. And if the operation returns a reference to data inside the temporary. It shouldn't affect the original object book in any way. But the result is
The object changed!



The behavior is *undefined*. Anything could happen, and among valid

outcomes can be that the original object changes. An explanation to it

might be that the compiler guesses what you intended to do and changes

the original object instead, but that's still *undefined* and you should

not rely on it. Read up on undefined behavior.



V

OK, I got it!
 
J

junyangzou

[...] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It

. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
I am using g++. And if the operation returns a reference to data inside the temporary. It shouldn't affect the original object book in any way. But the result is
The object changed!



The behavior is *undefined*. Anything could happen, and among valid

outcomes can be that the original object changes. An explanation to it

might be that the compiler guesses what you intended to do and changes

the original object instead, but that's still *undefined* and you should

not rely on it. Read up on undefined behavior.



V

OK, got it! Thank you~!!
 
J

James Kanze

I am using g++. And if the operation returns a reference to
data inside the temporary. It shouldn't affect the original
object book in any way. But the result is
The object changed!

This seems to be a bug in g++. Other compilers do make a copy.
VC++ goes into an endless recursion with static_cast<TextBook
const>. (I don't think that this is correct either. The
cv-qualifiers on an rvalue are ignored *if* the type is
a non-class type, but here it's a class type. So
static_cast<int>( i ) and static_cast<int const>( i ) are
identical. But since TextBook is a class type, the const should
be kept, so the [] on the results of the cast should call the
const version.)
 
J

James Kanze

On 8/13/2013 11:56 AM, junyangzou wrote:
[...] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It . ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
may seem to work, in simple cases, but you can't count on it.
I am using g++. And if the operation returns a reference to
data inside the temporary. It shouldn't affect the original
object book in any way. But the result is
The object changed!
The behavior is *undefined*. Anything could happen, and among valid
outcomes can be that the original object changes. An explanation to it
might be that the compiler guesses what you intended to do and changes
the original object instead, but that's still *undefined* and you should
not rely on it. Read up on undefined behavior.

That was my first reaction too. But when I tried to work out
what was really happening under the hood, I couldn't explain his
behavior, so I instrumented the class and tried it. There's a bug
in g++. Other compilers do make the copy.
 
J

James Kanze

On 8/13/2013 11:56 AM, junyangzou wrote:
] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It
. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
may seem to work, in simple cases, but you can't count on it.
I am using g++. And if the operation returns a reference to
data inside the temporary. It shouldn't affect the original
object book in any way. But the result is
: D
The object changed!
The behavior is *undefined*. Anything could happen, and among valid
outcomes can be that the original object changes. An explanation to it
might be that the compiler guesses what you intended to do and changes
the original object instead, but that's still *undefined* and you should
not rely on it. Read up on undefined behavior.
That was my first reaction too. But when I tried to work out
what was really happening under the hood, I couldn't explain his
behavior, so I instrumented the class and tried it. There's a bug
in g++. Other compilers do make the copy.

Of course, the undefined behavior is still there, which lets g++
off the hook in this case.
 
M

Melzzzzz

On 8/13/2013 11:56 AM, junyangzou wrote:
] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It
. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
may seem to work, in simple cases, but you can't count on it.
I am using g++. And if the operation returns a reference to
data inside the temporary. It shouldn't affect the original
object book in any way. But the result is
: D
The object changed!
The behavior is *undefined*. Anything could happen, and among
valid outcomes can be that the original object changes. An
explanation to it might be that the compiler guesses what you
intended to do and changes the original object instead, but that's
still *undefined* and you should not rely on it. Read up on
undefined behavior.

That was my first reaction too. But when I tried to work out
what was really happening under the hood, I couldn't explain his
behavior, so I instrumented the class and tried it. There's a bug
in g++. Other compilers do make the copy.

What other compilers? I have tried clang , g++ and icpc they all
return 'D'.
 
J

James Kanze

On 8/13/2013 11:56 AM, junyangzou wrote:
] And the since the function where
you do the operation ends up returning a reference to data
inside this temporary, you end up with undefined behavior. It
. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
may seem to work, in simple cases, but you can't count on it.
I am using g++. And if the operation returns a reference to
data inside the temporary. It shouldn't affect the original
object book in any way. But the result is
: D
The object changed!
The behavior is *undefined*. Anything could happen, and among
valid outcomes can be that the original object changes. An
explanation to it might be that the compiler guesses what you
intended to do and changes the original object instead, but that's
still *undefined* and you should not rely on it. Read up on
undefined behavior.
That was my first reaction too. But when I tried to work out
what was really happening under the hood, I couldn't explain his
behavior, so I instrumented the class and tried it. There's a bug
in g++. Other compilers do make the copy.
What other compilers? I have tried clang , g++ and icpc they all
return 'D'.

Visual C++ copies. The standard makes it quite clear that
`static_cast` should copy in this case. For non-class types,
whether it copies or not doesn't make a difference; because the
results are an rvalue, there's no way a conforming program can
tell. But for a class type, the constructor must be called.

With a fully instrumented version (trace messages in all
constructors, destructors, etc.): the results are even more
interesting. G++ does do the copy, and modifies the copy,
but...

G++ uses a CoW implementation of std::string. When you call
a non-const version of operator[] on a string, it recognizes the
possibility of a possible modification, and "isolates" the
actual data, so that it is not shared, and will never be shared
in the future (until, at least, there is some operation which
would invalidate the returned reference). When you call the
const version, however, it doesn't do this. So the temporary
(and also your book2) share the same implementation, and when
you modify the temporary, you also modify the original, *and*
book2. Even with the reference, "book[0] = 'D';" also modifies
book2.

This raises an interesting point. If you call the const [],
cast away the const, and modify the object, is this undefined
behavior or not (assuming, of course, that the original object
was not const). If this behavior is defined, a CoW
implementation of std::string becomes almost impossible.

(For what it's worth, I've always done the opposite: the
const operator[] would call the non-const:

char& operator[]( int i )
{
return text;
}
char const& operator[]( int i ) const
{
return const_cast<TextBook*>( this )->operator[]( i );
}

Which would work, but would result in isolating the image when
it wasn't necessary.)

At any rate, your example is another point to consider in the
arguments for and against CoW.
 
M

Melzzzzz

With a fully instrumented version (trace messages in all constructors,
destructors, etc.): the results are even more interesting. G++ does do
the copy, and modifies the copy, but...

G++ uses a CoW implementation of std::string.

Aaah, that's it. All compilers I use link with same libstdc++
 
D

darylew

On Thursday, August 15, 2013 8:04:14 AM UTC-4, James Kanze wrote:
[SNIP]
(For what it's worth, I've always done the opposite: the
const operator[] would call the non-const:

char& operator[]( int i )
{
return text;
}
char const& operator[]( int i ) const
{
return const_cast<TextBook*>( this )->operator[]( i );
}

Which would work, but would result in isolating the image when
it wasn't necessary.)


I only done it once or twice, but I do it the way the OP does it. I sometimes
make the const version constexpr and I don't want to keep looking up how the
*_casts interact (i.e. possibly disqualify) with constexpr.

Daryle W.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,768
Messages
2,569,574
Members
45,048
Latest member
verona

Latest Threads

Top