Valid C++?

  • Thread starter andrew queisser
  • Start date
J

Jack Klein

* Rolf Magnus:
Alf said:
* Victor Bazarov:
andrew queisser wrote:
Is this code below valid C++? I'd like to use this construct but I'm
not sure if it'll be portable.

struct foo
{
char x[128];
};
struct bar
{
char sameSizeAsFooX[ sizeof ((foo *)0)->x ];
It is OK, I guess. Seems rather dangerous though, like dereferencing
a null pointer. Perhaps it would be less scary to do

char sameSizeAsFoox[ sizeof foo().x ];

(although it does require for 'foo' to be default-constructible while
your solution does not).

};
On the one hand, dereferencing a null-pointer is formally UB no matter
which context (except in a typeid expression).

Actually, the exception covers not only typeid, but also sizeof:

"An expression is potentially evaluated unless either it is the operand of
the sizeof operator (5.3.3), or it is the operand of the typeid operator
and does not designate an lvalue of polymorphic class type (5.2.8)."

No, that isn't the exception that applies to typeid.

And no, it doesn't matter whether a dereferencing is potentially
evaluated or not.

The statement above makes no sense. I have no idea what "potentially
evaluated" means.

Consider:

int main()
{
int x = 0;
int *ip = 0;
if (x)
{
x = *ip;
}
return x;
}

This program has completely defined behavior, and in fact main() will
return 0. The program contains an expression that would cause UB,
namely dereferencing a null pointer. But dereferencing a null (or
otherwise invalid) pointer can only cause UB if it is actually
executed at run time. That cannot happen in the sample as written,
because the expression is never evaluated.

The standard guarantees that if the operand of sizeof operator is an
expression, that expression is not evaluated. The expression is only
parsed, at compile time, to determine the type of the expression.
Since it may only be applied to complete types, the compiler knows the
size of the object representation from the type. The pointer is not
dereferenced, and in fact does not actually exist at compile time.

All of these are perfectly legal and defined:

sizeof 1/0; // yields sizeof(int)
sizeof 100.0/0 // yields sizeof(double)

....and given:

struct z { int x; double y );
struct z *zp = 0;

....then:

sizeof zp->y; // yields sizeof(double)

....is likewise perfectly legal, and so is:

sizeof (z*)0->y;

The actual temporary pointer is never formed, let along dereferenced.
 
A

Alf P. Steinbach

* Jack Klein:
* Rolf Magnus:
Alf P. Steinbach wrote:

* Victor Bazarov:
andrew queisser wrote:
Is this code below valid C++? I'd like to use this construct but I'm
not sure if it'll be portable.

struct foo
{
char x[128];
};
struct bar
{
char sameSizeAsFooX[ sizeof ((foo *)0)->x ];
It is OK, I guess. Seems rather dangerous though, like dereferencing
a null pointer. Perhaps it would be less scary to do

char sameSizeAsFoox[ sizeof foo().x ];

(although it does require for 'foo' to be default-constructible while
your solution does not).

};
On the one hand, dereferencing a null-pointer is formally UB no matter
which context (except in a typeid expression).
Actually, the exception covers not only typeid, but also sizeof:

"An expression is potentially evaluated unless either it is the operand of
the sizeof operator (5.3.3), or it is the operand of the typeid operator
and does not designate an lvalue of polymorphic class type (5.2.8)."
No, that isn't the exception that applies to typeid.

And no, it doesn't matter whether a dereferencing is potentially
evaluated or not.

The statement above makes no sense. I have no idea what "potentially
evaluated" means.

It's a term defined by the standard; the definition is quoted above the
sentence using that term.

"potentially evaluated" is in turn used to defined the term "used".

Which is limited to objects and functions.

Consider:

int main()
{
int x = 0;
int *ip = 0;
if (x)
{
x = *ip;
}
return x;
}

This program has completely defined behavior,
Yes.


[snip]
All of these are perfectly legal and defined:

sizeof 1/0; // yields sizeof(int)

Nope.

The standard says that if the second operand is zero, the result is
undefined behavior.

It doesn't distinguish sizeof expressions as exempt from that, and in
fact at least one very popular compiler, VC 7.1, chokes on the above.
 
A

Alf P. Steinbach

* Jack Klein:
* Victor Bazarov:
andrew queisser wrote:
Is this code below valid C++? I'd like to use this construct but I'm
not sure if it'll be portable.

struct foo
{
char x[128];
};
struct bar
{
char sameSizeAsFooX[ sizeof ((foo *)0)->x ];
It is OK, I guess. Seems rather dangerous though, like dereferencing
a null pointer. Perhaps it would be less scary to do

char sameSizeAsFoox[ sizeof foo().x ];

(although it does require for 'foo' to be default-constructible while
your solution does not).

};
On the one hand, dereferencing a null-pointer is formally UB no matter
which context (except in a typeid expression).

Yes, dereferencing a null pointer is undefined behavior. However
since the C++ standard (like the C standard) states:

"The sizeof operator yields the number of bytes in the object
representation of its operand. The operand is either an expression,
which is not evaluated, or a parenthesized typeid."

Since the expression is itself is not evaluated, the pointer is never
dereferenced, and the behavior is completely defined.

Nope.

Dereferenced doesn't mean "at run time".

Since it's UB the compiler can assign any meaning to it, including that
*p, when p == 0 is known at compile time, yields a 'double'.
 
O

Old Wolf

Alf said:
* Jack Klein:

Nope.

The standard says that if the second operand is zero, the result is
undefined behavior.

It doesn't distinguish sizeof expressions as exempt from that, and in
fact at least one very popular compiler, VC 7.1, chokes on the above.

I think he meant:
sizeof (1/0);
 
K

Kai-Uwe Bux

Alf said:
* Jack Klein:
* Victor Bazarov:
andrew queisser wrote:
Is this code below valid C++? I'd like to use this construct but I'm
not sure if it'll be portable.

struct foo
{
char x[128];
};
struct bar
{
char sameSizeAsFooX[ sizeof ((foo *)0)->x ];
It is OK, I guess. Seems rather dangerous though, like dereferencing
a null pointer. Perhaps it would be less scary to do

char sameSizeAsFoox[ sizeof foo().x ];

(although it does require for 'foo' to be default-constructible while
your solution does not).

};
On the one hand, dereferencing a null-pointer is formally UB no matter
which context (except in a typeid expression).

Yes, dereferencing a null pointer is undefined behavior. However
since the C++ standard (like the C standard) states:

"The sizeof operator yields the number of bytes in the object
representation of its operand. The operand is either an expression,
which is not evaluated, or a parenthesized typeid."

Since the expression is itself is not evaluated, the pointer is never
dereferenced, and the behavior is completely defined.

Nope.

Dereferenced doesn't mean "at run time".

Ok, but how do you argue that a dereferencing takes place at compile time?
The standard says the expression is not evaluated, it does not say the
expression is evaluated at compile time.
Since it's UB the compiler can assign any meaning to it, including that
*p, when p == 0 is known at compile time, yields a 'double'.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* Kai-Uwe Bux:
Ok, but how do you argue that a dereferencing takes place at compile time?
The standard says the [sizeof] expression is not evaluated, it does not say the
expression is evaluated at compile time.

Consider

template< int x > struct S{ char c[x]; };

int main()
{
int y = sizeof( S<5/0> );
}

Can we allow for a teeny tiny little bit of evaluation, do you think?

Cheers,

- Alf
 
V

Victor Bazarov

Kai-Uwe Bux said:
Ok, but how do you argue that a dereferencing takes place at compile
time? The standard says the expression is not evaluated, it does not
say the expression is evaluated at compile time.

I think we need to account free-standing implementations and some C++
interpreters along with them. Undefined behaviour cannot be split
into "compile-time" and "run-time" for those, can it? Is there
anything in the Standard that prohibits an interpreting implementation?
An interpreter will dereference everything at compile time.

"Undefined behaviour" defined without splitting it into "undefined
behaviour for the compiler" and "undefined behaviour for the compiled
code", AFAICT. We may want it to be, there is an illusion that it is,
but it isn't. Or is it?

Or the compiler may format your hard drive instead...

V
 
V

Victor Bazarov

Alf said:
* Kai-Uwe Bux:
Ok, but how do you argue that a dereferencing takes place at compile
time? The standard says the [sizeof] expression is not evaluated, it does
not say the expression is evaluated at compile time.

Consider

template< int x > struct S{ char c[x]; };

int main()
{
int y = sizeof( S<5/0> );
}

Can we allow for a teeny tiny little bit of evaluation, do you think?

'S<5/0>' is not an expression that is "not evaluated". It's a type-id.
5.3.3/1 clearly delineates the two.

There *is* a difference between const expressions which *need* to be
evaluated (especially during instantiating of a template) and const
expressions sitting directly in the sizeof.

V
 
A

Alf P. Steinbach

* Victor Bazarov:
Alf said:
* Kai-Uwe Bux:
Ok, but how do you argue that a dereferencing takes place at compile
time? The standard says the [sizeof] expression is not evaluated, it does
not say the expression is evaluated at compile time.
Consider

template< int x > struct S{ char c[x]; };

int main()
{
int y = sizeof( S<5/0> );
}

Can we allow for a teeny tiny little bit of evaluation, do you think?

'S<5/0>' is not an expression that is "not evaluated". It's a type-id.
5.3.3/1 clearly delineates the two.

Nope, 5/0 is an expression.

Anyway, you can make the whole sizeof argument a value producing
expression if you want.

So that distinction is irrelevant: it's been demonstrated that there is
evaluation.

There *is* a difference between const expressions which *need* to be
evaluated (especially during instantiating of a template) and const
expressions sitting directly in the sizeof.

Yes, I agree, but not with regard to the formal rules: it's a QOI issue.
 
K

Kai-Uwe Bux

Victor said:
I think we need to account free-standing implementations and some C++
interpreters along with them. Undefined behaviour cannot be split
into "compile-time" and "run-time" for those, can it? Is there
anything in the Standard that prohibits an interpreting implementation?
An interpreter will dereference everything at compile time.

The standard says the expression is not evaluated. An interpreter is not
supposed to evaluate it either. So why/when would dereferencing occur?
"Undefined behaviour" defined without splitting it into "undefined
behaviour for the compiler" and "undefined behaviour for the compiled
code", AFAICT. We may want it to be, there is an illusion that it is,
but it isn't. Or is it?

Your arguing a point never disputed. What is under dispute is whether

sizeof( ((foo*)0)->x )

involves dereferencing. Alf seems to read the phrase "the expression is not
evaluated" to mean "possibly evaluated at compile time". That is what
triggered my question.
Or the compiler may format your hard drive instead...

Only if it actually is UB, which is under dispute.


Best

Kai-Uwe Bux
 
K

Kai-Uwe Bux

Alf said:
* Victor Bazarov:
Alf said:
* Kai-Uwe Bux:
Ok, but how do you argue that a dereferencing takes place at compile
time? The standard says the [sizeof] expression is not evaluated, it
does not say the expression is evaluated at compile time.
Consider

template< int x > struct S{ char c[x]; };

int main()
{
int y = sizeof( S<5/0> );
}

Can we allow for a teeny tiny little bit of evaluation, do you think?

'S<5/0>' is not an expression that is "not evaluated". It's a type-id.
5.3.3/1 clearly delineates the two.

Nope, 5/0 is an expression.

But S said:
Anyway, you can make the whole sizeof argument a value producing
expression if you want.

In that case, no evaluation of that expression is supposed to take place.
So that distinction is irrelevant: it's been demonstrated that there is
evaluation.

It has not been demonstrated.


Best

Kai-Uwe Bux
 
A

Alf P. Steinbach

* Kai-Uwe Bux:
Alf seems to read the phrase "the expression is not
evaluated" to mean "possibly evaluated at compile time".

Well no, there is no question that some evaluation goes on at compile
time for a sizeof expression: what the phrase "is not evaluated" means
in §5.3.3/1 is simply that there is no run-time effect.

Example (it's essentially the same as before):

template< int x > struct S { typedef int T; };
template<> struct S<1> { typedef double T; };
template<> struct S<2> { typedef char T; }

int const y = sizeof( S<5/3>::T() ); // Expect sizeof(double).

If there was no value evaluation, then sizeof simply couldn't deliver.

The added complication here just to make the sizeof argument formally an
expression instead of a type-id.

If that was the hurdle?
 
A

Alf P. Steinbach

* Kai-Uwe Bux:
Alf said:
* Victor Bazarov:
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
Ok, but how do you argue that a dereferencing takes place at compile
time? The standard says the [sizeof] expression is not evaluated, it
does not say the expression is evaluated at compile time.
Consider

template< int x > struct S{ char c[x]; };

int main()
{
int y = sizeof( S<5/0> );
}

Can we allow for a teeny tiny little bit of evaluation, do you think?
'S<5/0>' is not an expression that is "not evaluated". It's a type-id.
5.3.3/1 clearly delineates the two.
Nope, 5/0 is an expression.

But S said:
Anyway, you can make the whole sizeof argument a value producing
expression if you want.

In that case, no evaluation of that expression is supposed to take place.
So that distinction is irrelevant: it's been demonstrated that there is
evaluation.

It has not been demonstrated.

Bah, you're just arguing.
 
N

Noah Roberts

Kai-Uwe Bux said:
But S<5/0> is not.

You are correct. S<5/0> is a type. However, to get that type the
compiler will evaluate the *expression* 5/0.


template<int T>
struct X {};

int main(void)
{
X<10/2> x1;
X<5> x2;
X<10/5> x3;

x1 = x2;
// x1 = x3; no conversion between X<2> to X<5>

// X<5/0> x4; -> error C2975: 'T' : invalid template argument for 'X',
compile-time evaluatable constant expression expected.
}


Now, MSVC++ could be wrong here but I believe the standard specifies
that it must be a compile time evaluatable constant expression.

I suspect that you already know this so I must arrive at the same
conclusion of Alf - you are just being argumentative.
 
K

Kai-Uwe Bux

Noah said:
You are correct. S<5/0> is a type. However, to get that type the
compiler will evaluate the *expression* 5/0.

That is correct, however, the original problem is whether

sizeof( ((foo*)0)-x )

involves dereferencing.
template<int T>
struct X {};

int main(void)
{
X<10/2> x1;
X<5> x2;
X<10/5> x3;

x1 = x2;
// x1 = x3; no conversion between X<2> to X<5>

// X<5/0> x4; -> error C2975: 'T' : invalid template argument for 'X',
compile-time evaluatable constant expression expected.
}


Now, MSVC++ could be wrong here but I believe the standard specifies
that it must be a compile time evaluatable constant expression.

I suspect that you already know this so I must arrive at the same
conclusion of Alf - you are just being argumentative.

I am not denying nor debating that template instantiation involves compile
time evaluations. I am just interested in the demonstration that

sizeof( ((foo*)0)->x )

involves dereferencing the 0 pointer despite the standards wording that no
evaluation of the expression takes place. I fail to see how the template
instantiation examples settle that question. Alf argues that some kind of
evaluation has to take place (as demonstrated by template examples) and
therefore all bets are off even in examples where no compelling argument
can be made. That involves a certain interpretation of the phrase "the
expression is not evaluated", namely that it just means that no observable
effects of such evaluation may take place at run-time. I am just not sure
yet whether I should buy into that interpretation of the standard (in
particular given Victors remarks that the standard allows for interpreters
where there is no distinction of compile-time and run-time). I just have to
think about this more. But I am not convinced yet.


Best

Kai-Uwe Bux
 
T

Tom Widmer

Kai-Uwe Bux said:
I am not denying nor debating that template instantiation involves compile
time evaluations. I am just interested in the demonstration that

sizeof( ((foo*)0)->x )

involves dereferencing the 0 pointer despite the standards wording that no
evaluation of the expression takes place. I fail to see how the template
instantiation examples settle that question. Alf argues that some kind of
evaluation has to take place (as demonstrated by template examples) and
therefore all bets are off even in examples where no compelling argument
can be made. That involves a certain interpretation of the phrase "the
expression is not evaluated", namely that it just means that no observable
effects of such evaluation may take place at run-time. I am just not sure
yet whether I should buy into that interpretation of the standard (in
particular given Victors remarks that the standard allows for interpreters
where there is no distinction of compile-time and run-time). I just have to
think about this more. But I am not convinced yet.

FWIW, I agree with you that it is not UB, since the dereferencing never
happens. Dereferencing a null pointer is short hand for evaluating an
expression that dereferences a null pointer, IMHO.

Tom
 
T

Tom Widmer

Alf said:
* Jack Klein:


Nope.

Dereferenced doesn't mean "at run time".

It does mean the dereference actually has to happen. sizeof expressions
are never evaluated.
Since it's UB the compiler can assign any meaning to it, including that
*p, when p == 0 is known at compile time, yields a 'double'.

By your argument, does the following code have UB?

if (false)
{
*static_cast<int*>(0) = 10;
}

Tom
 
A

Alf P. Steinbach

* Tom Widmer:
It does mean the dereference actually has to happen. sizeof expressions
are never evaluated.

Compile time constant expressions are evaluated, and examples have been
given in this thread where that yields different results (and UB)
depending on the expression.

So the "never" is incorrect.

Hence, the argument is invalid.

By your argument,

No, I don't claim ownership for any argument... ;-)

does the following code have UB?

if (false)
{
*static_cast<int*>(0) = 10;
}

Formally it has Undefined Behavior in the same way as

if( false )
{
5/0;
}

The compiler is free to produce /any/ code for the expression "5/0",
including treating it as requiring a 3 GiB helper object and allocate
that much space on the stack on entry to the function.

AFAICS there are only two ways out if you want the first to not be UB:
you can pinpoint where the standard differentiates pointer expressions
and integer expressions wrt. UB, so that they behave differently, or you
can pinpoint where the standard allows the second to not be formally UB.
 
T

Tom Widmer

Alf said:
* Tom Widmer:



Compile time constant expressions are evaluated

The expression the sizeof operator is acting on (((foo *)0)->x) is not a
constant expression (5.19). Such expressions cannot access values, as
the above one does.

, and examples have been
given in this thread where that yields different results (and UB)
depending on the expression.

So the "never" is incorrect.

Something that is 'not evaluated' is certainly never evaluated. I agree
that constant expressions do potentially need to be evaluated as part of
a sizeof in order to determine the type specified in the sizeof. e.g.

sizeof(std::bitset<4000/2>);

4000/2 has to be evaluated. But I think constant expressions are the
special case.
Hence, the argument is invalid.
>


Formally it has Undefined Behavior in the same way as

if( false )
{
5/0;
}

The compiler is free to produce /any/ code for the expression "5/0",
including treating it as requiring a 3 GiB helper object and allocate
that much space on the stack on entry to the function.

The two examples are not quite equivalent, since '5/0' is an integral
constant expression (maybe an ill-formed one?), though since it isn't
used where an integral constant expression is required, I'm not entirely
sure whether it should be evaluated as such (and in fact, I don't think
it matters). I think 1.9/5 makes it clear that it must be possible for
the operation to be part of a permissible execution sequence before the
program actually has UB.
AFAICS there are only two ways out if you want the first to not be UB:
you can pinpoint where the standard differentiates pointer expressions
and integer expressions wrt. UB, so that they behave differently, or you
can pinpoint where the standard allows the second to not be formally UB.

1.9/5 is my best shot, and I think it allows both examples.

Tom
 
A

Alf P. Steinbach

* Tom Widmer:
Something that is 'not evaluated' is certainly never evaluated. I agree
that constant expressions do potentially need to be evaluated as part of
a sizeof in order to determine the type specified in the sizeof. e.g.

sizeof(std::bitset<4000/2>);

4000/2 has to be evaluated. But I think constant expressions are the
special case.

(Note: the above isn't really a demonstration, because it's a type-id
argument, and the standard's comment about "evaluated" only applies to
sizeof expression argument; however, a corresponding expression arg
example is easily produced, and was shown earlier.)

Well, both evaluated and not evaluated is a contradiction.

It seems that you resolve it by postulating a special case for constant
expressions -- but where is that special case in the standard?

I resolve it by taking "evaluated" to mean "evaluated at run-time", in
that context (context: the standard's definition of sizeof).

I think Occam's razor would agree with me.

If it were still alive & sharp. ;-)


[snip]
I think 1.9/5 makes it clear that it must be possible for
the operation to be part of a permissible execution sequence before the
program actually has UB.

The question seems to be whether there is such a thing as pure compile
time UB: something that's never actually executed, can never be
executed, and yet is clearly defined as UB by the standard?

And yes, there is.

The first I found by simply searching for "undefined" was §2.1/1/2
(sub-para of para of section): if you use a backslash line continuation
to form universal character name, "the behavior is undefined".

So it's not the case that whatever causes UB, must be part of a
permissible execution sequence before the program actually has UB.

At least as I see it. :)
 

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,744
Messages
2,569,484
Members
44,903
Latest member
orderPeak8CBDGummies

Latest Threads

Top