What's the point of doing "typeid(1 ? *ptr : *ptr)" ?

  • Thread starter Johannes Schaub
  • Start date
J

Johannes Schaub

I have seen this code in the wild:

struct X {
// ...
bool isDerived() const {
return typeid(1 ? *base : *base) == typeid(Derived);
}
Base *base;
};

And I was wondering why that pointless (??) conditional operator is there.
It looks like it could be written as

return typeid(*base) == typeid(Derived);

Is there any difference at all?
 
A

Alf P. Steinbach

I have seen this code in the wild:

struct X {
// ...
bool isDerived() const {
return typeid(1 ? *base : *base) == typeid(Derived);
}
Base *base;
};

And I was wondering why that pointless (??) conditional operator is there.
It looks like it could be written as

return typeid(*base) == typeid(Derived);

Is there any difference at all?

The only thing I can think of is that with some compiler this thing
makes the compiler behave in a non-standard way and not detect
dereferencing of nullpointer, i.e., with some particular compiler to
suppress std::bad_typeid exception when `base` is 0, C++98 §5.2.8/2.

Perhaps.

:)


Cheers & hth.,

- Alf

PS: I visited SO again. I was horrified. There's such an abundance of
morons and Python programmers there, with intersection covering most of
both of sets. I think it's your fault. You're too intelligent, so to
keep mean intelligence of SO around 100, Allah has to put morons there.
 
K

Kalle Olavi Niemitalo

Johannes Schaub said:
And I was wondering why that pointless (??) conditional operator is there.
It looks like it could be written as

return typeid(*base) == typeid(Derived);

Is there any difference at all?

If base is NULL, then typeid(*base) must throw std::bad_typeid,
but typeid(1 ? *base : *base) is undefined. The author may have
been trying to speed up the function by telling the compiler that
the null-pointer check is not needed. This trick does not work
with gcc version 4.4.5 (Debian 4.4.5-8) though, which generates
the check anyway. There, typeid(static_cast<Base &>(*base))
appears to work instead.
 
A

Alf P. Steinbach

If base is NULL, then typeid(*base) must throw std::bad_typeid,
but typeid(1 ? *base : *base) is undefined.

No, it isn't.

See my answer from yesterday.

The author may have
been trying to speed up the function by telling the compiler that
the null-pointer check is not needed.

Yes.

See my answer from yesterday.

This trick does not work
with gcc version 4.4.5 (Debian 4.4.5-8) though, which generates
the check anyway. There, typeid(static_cast<Base&>(*base))
appears to work instead.

It's relying on non-standard behavior, that is, bugs, in the compiler.


Cheers & hth.,

- Alf
 
J

Johannes Schaub

wrote:
No, it isn't.

See my answer from yesterday.

I think he's right. In "1 ? *base : *base" the lvalue was obtained from the
conditional operator expression, and not from a dereference operator.

Clang omits the null pointer check and I think it is allowed to do so.
 
A

Alf P. Steinbach

wrote:


I think he's right. In "1 ? *base : *base" the lvalue was obtained from the
conditional operator expression, and not from a dereference operator.

`typeid` takes an `expression` as argument.

In `*base` the lvalue is obtained from
a `unary-expression`, which is
a `cast-expression`, which is
a `pm-expression`, which is
a `multiplicative-expression`, which is
an `additive-expression`, which is
a `shift-expression`, which is
a `relational-expression`, which is
an `equality-expression`, which is
an `and-expression`, which is
an `exclusive-or-expression`, which is
a `logical-and-expression`, which is
a `logical-or-expression`, which is
a `conditional-expression`, which is
an `assignment-expression`, which is
an `expression` (phew!).

In `1 ? *base : *base` the lvalue is obtained from
a `conditional-expression`, which is
an `assignment-expression`, which is
an `expression` (phew!).

Note that *both* parse trees include a `conditional-expression`.

Note also that the Holy Standard does not mention anything about
syntactical category.

Arguing that `conditional-expression` is not allowed is therefore a bit
absurd, like, twice absurd, don't you think?

Clang omits the null pointer check and I think it is allowed to do so.

No, it's not allowed by C++98.

I haven't checked C++0x.


Cheers & hth.,

- Alf
 
C

Christopher

I have seen this code in the wild:

    struct X {
      // ...
      bool isDerived() const {
        return typeid(1 ? *base : *base) == typeid(Derived);
      }
      Base *base;
    };

And I was wondering why that pointless (??) conditional operator is there..
It looks like it could be written as

    return typeid(*base) == typeid(Derived);

Is there any difference at all?


If I read that code, I'd go yell at someone. If there is a point, it
isn't very explicit.
 
A

Alf P. Steinbach

wrote:


It says specifically "If the lvalue expression is obtained by applying the
unary * operator to a pointer". That's not the case for "1 ? *ptr : *ptr".

Consider an expression such as `base[0]`.

Do you think the authors of the Holy Standard meant that there should be
no nullpointer checking for `base[0]`, which is a syntactical form not
involving a manifest outermost application of *?

If you think no checking for base[0], then what do you think the PURPOSE
of the checking is then, that so much effort is expended on it?

But if you think yes of course also checking for base[0], then how is
that case covered by your interpretation of the standard as referring to
visual form?

Now that's your dilemma, that your interpretation does not make sense
technically, but only as support for an original position. It either
yields meaningless behavior (checking for *p, not for p[0]). Or else it
is completely arbitrary (p[0] arbitrarily included in category of unary
* applications, (1:*p:*p) not included).

The lvalue that's used is the lvalue that the conditional operator produces.
But for "*ptr", the lvalue is directly produced by applying unary * to a
pointer.

So, you mean that p[0] needs not be checked?

Then what do you think the purpose is of this checking?

E.g. how reliable is it as a diagnostic tool?

That difference is why the following difference exists in C++0x (and major
C++03 compilers):

struct A {
static int const value = 0;
};

int q2 = +A::value;
int q2 = +(1 ? A::value : A::value);

For the second case, you need a definition of A::value, which the code
doesn't provide. For the first case, you don't, because "the lvalue-to-
rvalue conversion (4.1) is immediately applied". In the second case it is
not applied immediately to the lvalue obtained by evaluating "A::value".

This is a defect in or shortcoming of the standard's notion of "used",
defined by the ODR rule. In C++98 it was horrible, not useful for
anything; that is, the practice did not rely on the standard in this
respect, the standard was impotent, and e.g. the Boost guys got
themselves a big surprise. In C++0x it has been cleaned up but is
perhaps still too defective for use in practice; we'll see.

In the above's q2 example A::value is an object that in the C++0x
standard's words "satisfies the requirements for appearing in a constant
expression" and where "the lvalue-to-rvalue conversion is immediately
applied". Which means that it's NOT "odr-used". Which means that you do
not need a formal definition (which, readers beware, is not the in-class
declaration providing a value, which formally here is just a
declaration!, but is a separate declaration outside the class body).

Since the point may not be very visible, lemme repeat: you do not
formally need a definition in C++0x, and in C++98 you only needed it due
to a defect, so in C++98 discussing the formal was meaningless.

Anyway, it's a fallacious diversion.

An example that the standard is ungood in some other way, as you provide
above, does not mean that it is also ungood wrt. `typeid`.


Cheers & hth.,

- Alf
 
J

Johannes Schaub

wrote:
wrote:


It says specifically "If the lvalue expression is obtained by applying
the unary * operator to a pointer". That's not the case for "1 ? *ptr :
*ptr".

Consider an expression such as `base[0]`.

Do you think the authors of the Holy Standard meant that there should be
no nullpointer checking for `base[0]`, which is a syntactical form not
involving a manifest outermost application of *?

The rule "the lvalue expression is obtained by applying the unary * operator
to a pointer" is both a semantic and a syntactic requirement. Where lvalues
are introduced from scratch and where not (i.e where an expression is just
stated equivalent to another one) is a semantic issue. For example "(*ptr)"
requires the null pointer check, because "(expr)" is equivalent to "expr".

For "base[0]" we have "The expression E1[E2] is identical (by definition) to
*((E1)+(E2)) ". So if the spec requires a null check for "*(base + 0)", it
requires a null check for "base[0]".
If you think no checking for base[0], then what do you think the PURPOSE
of the checking is then, that so much effort is expended on it?

I would just have said that the behavior is always undefined. I can only
guess here: Apparently the authors wanted to catch null pointers, but only
in the case where the compiler can easily prevent evaluating an lvalue from
a dereferenced null pointer at runtime (which means determining the object
identity where there is no object). In the cases specified by the spec, it
can do that: It just has to branch on whether the pointer is null or not at
runtime, and if null, throwing an expression before determining the object
identity. That way, it never evaluates the lvalue "*ptr" if ptr is null.
Now that's your dilemma, that your interpretation does not make sense
technically, but only as support for an original position. It either
yields meaningless behavior (checking for *p, not for p[0]). Or else it
is completely arbitrary (p[0] arbitrarily included in category of unary
* applications, (1:*p:*p) not included).

If the compiler wanted to support arbitrarily complex expressions such as
"rand() % 2 ? *ptr : somevariable", it would first have to determine the
object identity (i.e the object identity would either be derived from the
lvalue "*ptr" or "somevariable") and then determine whether we have or don't
have an object identity. There's a contradiction here. An lvalue refers to
an object or function. Its evaluation can't result in refering to nothing.

Therefor I see a good reason to exclude arbitrarily complex expressions.
This is a defect in or shortcoming of the standard's notion of "used",
defined by the ODR rule. In C++98 it was horrible, not useful for
anything; that is, the practice did not rely on the standard in this
respect, the standard was impotent, and e.g. the Boost guys got
themselves a big surprise. In C++0x it has been cleaned up but is
perhaps still too defective for use in practice; we'll see.

Which is why I said it only makes a difference in C++0x. It's on the way to
be solved, but it's not yet solved. See http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_active.html#712 .
In the above's q2 example A::value is an object that in the C++0x
standard's words "satisfies the requirements for appearing in a constant
expression" and where "the lvalue-to-rvalue conversion is immediately
applied".

The lvalues that are introduced for "1 ? A::value : A::value" are

lvalue for "1 ? A::value : A::value"
^
/ \
lvalue for "A::value" lvalue for "A::value"


The lvalue to rvalue conversion is not applied directly to the lvalue that
results for the name "A::value".

An example that the standard is ungood in some other way, as you provide
above, does not mean that it is also ungood wrt. `typeid`.

The pointer I want to make is that there are other cases where the spec
differentiates between applying an operation to one lvalue and applying to
another lvalue that merely depends on the former one but is not equivalent
to it.
 
A

Alf P. Steinbach

wrote:
[snip]
That difference is why the following difference exists in C++0x (and
major C++03 compilers):

struct A {
static int const value = 0;
};

int q2 = +A::value;
int q2 = +(1 ? A::value : A::value);

For the second case, you need a definition of A::value, which the code
doesn't provide. For the first case, you don't, because "the lvalue-to-
rvalue conversion (4.1) is immediately applied". In the second case it is
not applied immediately to the lvalue obtained by evaluating "A::value".

This is a defect in or shortcoming of the standard's notion of "used",
defined by the ODR rule. In C++98 it was horrible, not useful for
anything; that is, the practice did not rely on the standard in this
respect, the standard was impotent, and e.g. the Boost guys got
themselves a big surprise. In C++0x it has been cleaned up but is
perhaps still too defective for use in practice; we'll see.

Which is why I said it only makes a difference in C++0x. It's on the way to
be solved, but it's not yet solved. See http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_active.html#712 .

I do not know whether to laugh or cry.

The issue that's pointed out, is non-existent: it is a case of choosing
an artificial meaningless interpretation, where a meaningful
interpretation is the only natural one.

The proposed resolution, on the other hand, has a lot of very unpleasant
issues -- wtf. is going on?

The lvalues that are introduced for "1 ? A::value : A::value" are

lvalue for "1 ? A::value : A::value"
^
/ \
lvalue for "A::value" lvalue for "A::value"

The lvalue to rvalue conversion is not applied directly to the lvalue that
results for the name "A::value".

My God.

An lvalue is a source code *expression*.

And in this case (?:) can't itself be an lvalue, so there is no way to
avoid the conversion being direct and immediate. You can't pass lvalues
around to functions or whatever. They are source code expressions.

The pointer I want to make is that there are other cases where the spec
differentiates between applying an operation to one lvalue and applying to
another lvalue that merely depends on the former one but is not equivalent
to it.

Thanks for the link.

I knew that the process was political, but never would have I believed
this to be possible.

It is food for thought.


Cheers,

- Alf

PS: I chose to only address this latest issue, which I think is the most
important. How did we come to choose such politicians? Same question,
same issue.
 
A

Alf P. Steinbach

wrote:
On 02.08.2011 22:29, Johannes Schaub wrote:
wrote: [snip]

That difference is why the following difference exists in C++0x (and
major C++03 compilers):

struct A {
static int const value = 0;
};

int q2 = +A::value;
int q2 = +(1 ? A::value : A::value);

For the second case, you need a definition of A::value, which the code
doesn't provide. For the first case, you don't, because "the lvalue-to-
rvalue conversion (4.1) is immediately applied". In the second case
it is
not applied immediately to the lvalue obtained by evaluating
"A::value".

This is a defect in or shortcoming of the standard's notion of "used",
defined by the ODR rule. In C++98 it was horrible, not useful for
anything; that is, the practice did not rely on the standard in this
respect, the standard was impotent, and e.g. the Boost guys got
themselves a big surprise. In C++0x it has been cleaned up but is
perhaps still too defective for use in practice; we'll see.

Which is why I said it only makes a difference in C++0x. It's on the
way to
be solved, but it's not yet solved. See http://www.open-
std.org/jtc1/sc22/wg21/docs/cwg_active.html#712 .

I do not know whether to laugh or cry.

The issue that's pointed out, is non-existent: it is a case of choosing
an artificial meaningless interpretation, where a meaningful
interpretation is the only natural one.

The proposed resolution, on the other hand, has a lot of very unpleasant
issues -- wtf. is going on?

The lvalues that are introduced for "1 ? A::value : A::value" are

lvalue for "1 ? A::value : A::value"
^
/ \
lvalue for "A::value" lvalue for "A::value"

The lvalue to rvalue conversion is not applied directly to the lvalue
that
results for the name "A::value".

My God.

An lvalue is a source code *expression*.

And in this case (?:) can't itself be an lvalue, so there is no way to
avoid the conversion being direct and immediate. You can't pass lvalues
around to functions or whatever. They are source code expressions.

The pointer I want to make is that there are other cases where the spec
differentiates between applying an operation to one lvalue and
applying to
another lvalue that merely depends on the former one but is not
equivalent
to it.

Thanks for the link.

I knew that the process was political, but never would have I believed
this to be possible.

It is food for thought.


Cheers,

- Alf

PS: I chose to only address this latest issue, which I think is the most
important. How did we come to choose such politicians? Same question,
same issue.

Oh, I'm sorry for pointing that outburst at the wrong details. Oops. Mea
culpa. "in this case (?:) can't itself be an lvalue" was wrong.

The standard does indeed, as the DR indicates, allow things like

int a = 1;
int b = 2;
bool x = whatever;

(x? a : b) = 666; // <- Hey, this is actually supported.

Now that was pretty surprising to me!

The DR submission points to that Real Issue, but since I have never ever
seen that C construct used, and since the proposed resolution doesn't
fix that and is so complex that nobody could remember or relate to it, I
did neither believe the claim that the above is supported, nor did I
check it out. But now, checking it, yikes!, it's true! (?:) can yield an
lvalue even when the types are not explicitly references.

Happily the relevant part of the standard is only ONE LINE, §5.16/4.

It would be very easy to change it to what modern programmers expect,
without affecting much (if any) existing code -- as opposed to the
removal of implicit conversion to char* that was done.

And the changed text would be easy to understand, as opposed to the
current resolution's long-winded morass of details (over ten to fifteen
lines) that no practicing programmer will ever relate to.


Cheers,

- Alf
 
J

James Kanze

On 01.08.2011 21:22, Johannes Schaub wrote:
[...]
PS: I visited SO again. I was horrified. There's such an abundance of
morons and Python programmers there, with intersection covering most of
both of sets. I think it's your fault. You're too intelligent, so to
keep mean intelligence of SO around 100, Allah has to put morons there.

Thanks:). But I understand what you mean: the site gives a
bonus for a quick response, not for a correct or a complete
response. So you get people responding quickly, even when they
don't know what they're talking about.

On the other hand, some of us can't access Google groups from
work, and we have to have something to do waiting for compiles
to finish.
 

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,755
Messages
2,569,536
Members
45,014
Latest member
BiancaFix3

Latest Threads

Top