std::string name4 = name4;

L

Lynn McGuire

std::string name4 = name4;

Shouldn't this line of code generate a compiler error ?
It does not in Visual Studio 2005.

Lynn
 
F

Felix Palmen

* Lynn McGuire said:
std::string name4 = name4;

Shouldn't this line of code generate a compiler error ?
It does not in Visual Studio 2005.

Without explicitly looking at the standard:

| sdt::string name4

declares and instantiates name4 by calling its default constructor, the
content is initialized to an empty string.

I assume it's syntactically correct to call operator=() on itself.

Regards, Felix
 
N

Niels Dekker - no reply address

Lynn said:
std::string name4 = name4;

Shouldn't this line of code generate a compiler error ?

FYI, I posted a very similar question to comp.std.c++, December 2006.
Subject: "Copy-initialization by itself (T a = a) allowed?"
http://www.cpptalk.net/image-vp457183.html

Apparently your line of code does not need to generate a compiler error.

HTH,

Niels
 
F

Felix Palmen

* Leigh Johnston said:
No, operator=() is used for assignment and "foo a = b;" is not assignment,
it is copy construction (the compiler is allowed to elide the copy I think)
which I guess makes the statement in question undefined behaviour.

Ok, you are absolutely correct, used this test code to verify it:

| #include <iostream>
|
| class X
| {
| public:
|
| X();
| X(const X &rhs);
| void operator=(const X &rhs);
| };
|
| X::X()
| {
| std::cout << "default constructor" << std::endl;
| }
|
| X::X(const X &rhs)
| {
| std::cout << "copy constructor from "
| << std::hex << &rhs << " to "
| << std::hex << this << std::endl;
| }
|
| void X::eek:perator=(const X &rhs)
| {
| std::cout << "assignment operator" << std::endl;
| }
|
| int main(int argc, char **argv)
| {
| X foo = foo;
| }

Output is:
| copy constructor from 0x7fff4f6b6a0f to 0x7fff4f6b6a0f

Still, g++ doesn't have any objections against that code. Obviously, it
calls the copy constructor on itself...

Regards, Felix
 
Ö

Öö Tiib

Ok, you are absolutely correct, used this test code to verify it:

...
Still, g++ doesn't have any objections against that code. Obviously, it
calls the copy constructor on itself...

Yes, and that is like standard tells it should do. Self-initializing
is almost always bug (like delete this). You can use it as a trick to
confuse the crap out of readers of the code and achieve some effect
that almost never matters.

When it is typo and you want to prevent such errors you have to use
some static code analyzer that warns about self construction and other
ways of possible usage of possibly uninitialized variables. Even when
the warning is false positive it likely indicates code that is tricky
to read for human too.
 
N

Niels Dekker - no reply address

Pete said:
struct node
{
int value;
node *next;
};

node n = { 1, &n };

This creates and initializes a node that points to itself. That's a
legitimate use; prohibiting using a name inside its initializer would
ban useful code in order to prevent an error that's obvious from
inspection.

Nice example, thank Pete. However, one /might/ consider prohibiting
during copy-construction. Or do you think the following could also be
useful code?

node n = n;


Kind regards,

Niels
 
Ö

Öö Tiib

struct node
{
int value;
node *next;

};

node n = { 1, &n };

This creates and initializes a node that points to itself. That's a
legitimate use; prohibiting using a name inside its initializer would
ban useful code in order to prevent an error that's obvious from
inspection.

Yes, what i meant was self-initializing like "T a = a;" is like
"delete this" legitimate, but usually a bug. Someone surely can
construct meaningful usage for "T a = a;" too.

I did not mean taking references for constructing self-references or
circular references.
 
B

Balog Pal

Pete Becker said:
It's always possible to complicate the language to prohibit stupid code.
The question is whether it's worthwhile. Even without prohibiting it,
compilers can issue a warning.

Indeed. Some compilers emit warnings "variable used before init" in
optimizing mode. Wonder if this construct triggers that particular warning.
It should.
 
F

Francesco S. Carta

std::string name4 = name4;

Shouldn't this line of code generate a compiler error ?
It does not in Visual Studio 2005.

With MinGW 4.4.0 the code above has generated a runtime exception from
basic_string - but I guess it has been a coincidence, because it doesn't
happen always. Using that instance afterwards seems more likely to raise
an exception.

This is a topic that turned up really lately also on clc++m, this was my
follow-up there:
http://groups.google.it/group/comp.lang.c++.moderated/msg/fdd504812933771c

Unfortunately nobody replied to me, there, but I think I've come to
understand - more or less - what this issue is all about, and I'm happy
this topic was raised here because I'd like to have my conclusions
reviewed by the group.

If I got it straight, a direct or indirect self-reference in the
construction statement is fine in the following cases:

case A: the involved type is a fundamental type AND the instance has
static lifetime (the instance will always be zero-initialized as first step)

case B: the involved type is not a fundamental type AND

case B1: the self reference does NOT involve access to the uninitialized
data members where these members are part of the type invariant

case B2: the self reference involves access to the uninitialized data
members where these members are NOT part of the type invariant

case C: the involved type is a POD type AND the instance has static
lifetime (all the members will always be zero-initialized as first step)

Breaking any of those rules should lead to undefined behaviour.

[this post advances /no claim/ about being rigorous or complete, the
list of cases above can surely be extended and refined, it seems to me
that case C could be merged to case A or be a special case of case B]

Some examples following and breaking the mentioned cases:

//-------
#include <iostream>
#include <string>

using namespace std;

struct Self {
int data;

Self() : data(0) {
cout << "Self()" << endl;
}

Self(const Self& s) : data(s.data) {
cout << "Self(const Self&)" << endl;
}

Self(int data) : data(data) {
cout << "Self(int)" << endl;
}

};

template<class T> T* new_if_null(T* ptr) {
if (!ptr) {
cout << "ptr is null, initializing ptr" << endl;
ptr = new T;
*ptr = 42;
} else {
cout << "ptr is not null!" << endl;
}
return ptr;
}

// case A
int* global_p_int = new_if_null(global_p_int);
// prints "ptr is null, initializing ptr"

// case A
string* global_p_string = new_if_null(global_p_string);
// prints "ptr is null, initializing ptr"

int main() {

cout << "*global_p_int == " << *global_p_int << endl;
// prints 42

cout << "*global_p_string == " << *global_p_string << endl;
// prints *
// (at least on my system, where '*' == 42)

// case A
static char* static_p_char = new_if_null(static_p_char);
// prints "ptr is null, initializing ptr"

cout << "*static_p_char == " << *static_p_char << endl;
// prints *
// (at least on my system, where '*' == 42)

// case B2
Self b = b;
// prints Self(const Self&)
cout << "b.data == " << b.data << endl;
// prints some junk data

// case B2
Self c = c.data;
// prints Self(int)
cout << "c.data == " << c.data << endl;
// prints some other junk data

// case C
static Self static_b = static_b;
// prints Self(const Self&)
cout << "static_b.data == " << static_b.data << endl;
// prints 0

// case C
static Self static_c = static_c.data;
// prints Self(int)
cout << "static_c.data == " << static_c.data << endl;
// prints 0

// ====================================================
cout << endl << "UB cases: " << endl;

cout << "calling new_if_null(local_p_int):" << endl;

// breaks case A
int* local_p_int = new_if_null(local_p_int);
// prints "ptr is not null!"

cout << "local_p_int == " << local_p_int << endl;
// prints a junk address

cout << "*local_p_int == " << *local_p_int << endl;
// UB, could lead to memory access error or whatever

// breaks case B1
string s = s;
// called string(const string&)
// internal data is junk just like for "Self b(b);"

cout << s << endl;
// UB, s data members contain junk
// and will break std::string invariant
// could lead to some std::basic_string exception
}
//-------
 
Ö

Öö Tiib

Indeed. Some compilers emit warnings "variable used before init" in
optimizing mode. Wonder if this construct triggers that particular warning.
It should.

With MS compilers:

std::string s = s; // no warning
int i = i; // C4700: uninitialized local variable 'i' used
 
J

Juha Nieminen

Lynn McGuire said:
std::string name4 = name4;

Shouldn't this line of code generate a compiler error ?

AFAIK, no. You can initialize a variable with itself, which results
in undefined behavior. (I don't really know *why* the standard allows
that. Perhaps the committee figured out that trying to demand a diagnostic
would be very hard or even impossible in the general case. Problematic
situations arise in surprising places sometimes.)

This is equally "valid" (in syntax, but still undefined behavior):

int i = i;
 
F

Francesco S. Carta

AFAIK, no. You can initialize a variable with itself, which results
in undefined behavior. (I don't really know *why* the standard allows
that. Perhaps the committee figured out that trying to demand a diagnostic
would be very hard or even impossible in the general case. Problematic
situations arise in surprising places sometimes.)

This is equally "valid" (in syntax, but still undefined behavior):

int i = i;

As far as I understood this (see my other post in this thread for
further lucubrations), if "i" has static lifetime, then "i" will be
initialized to zero and then [*] copy-created from zero again - well
defined behaviour then.

If it has not static lifetime then it gets some junk value - and that
/value/ would be undefined, of course, but the object is still valid and
the implementation should not be free to invoke UB and to format my hard
disk if I happen to use an non-itialized or junk-initialized integer for
something trivial as printing it out :)

[*] I know it seems silly said in this way, but in this case the
initialization follows a double step and there is an initialization
before the copy-constructor gets called - in this case we are dealing
with a primitive type and this seems even more silly, but the overall
behaviour should be like that, please take this as a layman explanation.
 
B

Balog Pal

Francesco S. Carta said:
int i = i;

As far as I understood this (see my other post in this thread for further
lucubrations), if "i" has static lifetime, then "i" will be initialized to
zero and then [*] copy-created from zero again - well defined behaviour
then.

If it has not static lifetime then it gets some junk value - and that
/value/ would be undefined, of course, but the object is still valid and
the implementation should not be free to invoke UB and to format my hard
disk if I happen to use an non-itialized or junk-initialized integer for
something trivial as printing it out :)

The initialization involves an lvalue-to-rvalue conversion of object i, that
is at that point not initialized. The Standard explicitly states that being
UB, so that's it. "Should"s may still play on the QoI field certainly,
conforming implementation has unlimited license to do any good thing as bad.
 
F

Francesco S. Carta

Francesco S. Carta said:
int i = i;

As far as I understood this (see my other post in this thread for
further lucubrations), if "i" has static lifetime, then "i" will be
initialized to zero and then [*] copy-created from zero again - well
defined behaviour then.

If it has not static lifetime then it gets some junk value - and that
/value/ would be undefined, of course, but the object is still valid
and the implementation should not be free to invoke UB and to format
my hard disk if I happen to use an non-itialized or junk-initialized
integer for something trivial as printing it out :)

The initialization involves an lvalue-to-rvalue conversion of object i,
that is at that point not initialized. The Standard explicitly states
that being UB, so that's it. "Should"s may still play on the QoI field
certainly, conforming implementation has unlimited license to do any
good thing as bad.

I suppose your reference goes to the non-static i instance, and in such
case I'm not at all sure whether it is UB or not, although it might very
well be - I'd like to be referenced with the appropriate section of the
standard in order to clarify this.

About the static lifetime instance, times ago I was presented by James
Kanze with an example of how to instantiate a singleton using exactly
this peculiarity:
http://groups.google.com/group/comp.lang.c++/msg/c5707d36e886fe3e

Before going on, I'd have to ask one thing: is it guaranteed that
operator ! evaluates true when applied to a zero-initialized pointer?

In practice I want to know if given the following:

static int* p;

then:

!p == true

If it is guaranteed - and I think so - then I dare to claim that (I
can't believe I'm starting over arguing about the standard, but anyway):

1)
int i = i;
is well defined at global scope

2)
static int i = i;
is well defined in any scope where the statement is legal

3)
int* get_p();
int* p = get_p();
int* get_p() {
if(!p) p = new int;
return p;
}
is well defined at global scope

4)
given:
int* get_p(int* p) {
if(!p) p = new int;
return p;
}
then:
static int* p = get_p(p);
is well defined in any scope where the statement is legal


Are my claims correct?
 
J

James Kanze

No, although a warning might be appropriate.
AFAIK, no. You can initialize a variable with itself, which
results in undefined behavior.

Does it? It really depends on what you do with the variable.
Consider something like:

class List
{
List* next;
public:
List(List& olderInstance) : next( &olderInstance ) {}
// ...
};

Given something like this,
List aList( aList );
poses no problems. (Provided, e.g. that the code following the
pointers recogizes a list pointing to itself as the end of the
list.)

Something like:
std::string a(a);
is undefined behavior, because the constructor of string
actually accesses its argument, and doesn't just take the
address of it.
 
J

James Kanze

"Francesco S. Carta" <[email protected]>
int i = i;
As far as I understood this (see my other post in this
thread for further lucubrations), if "i" has static
lifetime, then "i" will be initialized to zero and then [*]
copy-created from zero again - well defined behaviour then.
If it has not static lifetime then it gets some junk value -
and that /value/ would be undefined, of course, but the
object is still valid and the implementation should not be
free to invoke UB and to format my hard disk if I happen to
use an non-itialized or junk-initialized integer for
something trivial as printing it out :)
The initialization involves an lvalue-to-rvalue conversion of
object i, that is at that point not initialized.

Unless the "object" has static lifetime, in which case it has
been zero initialized. (There are a few specialized idioms that
count on this, particularly when the object has a pointer type.)
 
B

Balog Pal

Francesco S. Carta said:
int i = i;

As far as I understood this (see my other post in this thread for
further lucubrations), if "i" has static lifetime, then "i" will be
initialized to zero and then [*] copy-created from zero again - well
defined behaviour then.

If it has not static lifetime then it gets some junk value - and that
/value/ would be undefined, of course, but the object is still valid
and the implementation should not be free to invoke UB and to format
my hard disk if I happen to use an non-itialized or junk-initialized
integer for something trivial as printing it out :)

The initialization involves an lvalue-to-rvalue conversion of object i,
that is at that point not initialized. The Standard explicitly states
that being UB, so that's it. "Should"s may still play on the QoI field
certainly, conforming implementation has unlimited license to do any
good thing as bad.

I suppose your reference goes to the non-static i instance,
Yes.

and in such case I'm not at all sure whether it is UB or not, although it
might very well be - I'd like to be referenced with the appropriate
section of the standard in order to clarify this.

Guess you could look up the precisely referred section. :-(

4.1 Lvalue to rvalue conversion [conv.lval]

1 An lvalue (3.10) of a nonfunction, nonarray type T can be converted to an
rvalue. If T is an incomplete type, a program that necessitates this
conversion is illformed. If the object to which the lvalue refers is not an
object of type T and is not an object of a type derived from T, or if the
object is uninitialized, a program that necessitates this conversion has
undefined behavior. If T is a nonclass type, the type of the rvalue is the
cvunqualified version of T. Otherwise, the type of the rvalue is T. 49)
Before going on, I'd have to ask one thing: is it guaranteed that operator
! evaluates true when applied to a zero-initialized pointer?

Yes, it should. "zero" init means semantic zero for any type. An non-local
objects are subject to value-initialization, so the above case does not
apply. If you fo after it make sure to use the 2003 text as it changed from
the '98 one.
 

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

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,070
Latest member
BiogenixGummies

Latest Threads

Top