what's the correct behaver about this ( conversion operator )

P

Peter Lee

what's the correct behaver about the following code ? ( C++
standard )
I got a very strange result....

class MyClass
{
public:
MyClass(const char* p)
{
printf("ctor p=%s\n", p);
}

operator const char*() const
{
printf("IN operator const char*() const\n");
return "abcd";
}
};


const char* MyTest()
{
printf("IN const char* MyTest()\n");

return _T("efg");
}



int test1(int v)
{

bool b = false;
MyClass s1( "obj1" );

const char* lp = NULL;
lp = (b ? s1 : MyTest()); // lp will be "abcd" !!! strange!?, it
create a temp object !?

// if write the follwing way lp will be "efg"

// if (b)
// {
// lp = s1;
// }
// else
// {
// lp = MyTest();
// }

printf("lp=%s\n", lp);


return 0;
}

// test in VC6.0

ctor p=obj1
IN const char* MyTest()
ctor p=efg
IN operator const char*() const
lp=abcd
 
I

Ian Collins

Peter said:
what's the correct behaver about the following code ? ( C++
standard )
I got a very strange result....

class MyClass
{
public:
MyClass(const char* p)
{
printf("ctor p=%s\n", p);
}

operator const char*() const
{
printf("IN operator const char*() const\n");
return "abcd";
}
};


const char* MyTest()
{
printf("IN const char* MyTest()\n");

return _T("efg");

What's _T? Post your real code.
 
A

antonov84

What a nice quiz question you've run into ;]. Look, expressions on
the both sides of : in ?: operator must resolve to same type. Now,
which way shall we go? Shall we try doing 2 'const char *' , or two
'MyClass' ? Both are available - second case is available through non-
explicit contructor:

MyClass(const char* p) // this adds implicit conversion from 'const
char*' to 'MyClass'

But why would compiler prefer second case (which requires additional
conversion back to 'const char*' when assigning) to the first? You may
want to try to understand what standarts says on the matter ;]

---------------------------------------------------------------------------------------

2 If either the second or the third operand has type (possibly cv-
qualified) void, then the lvalue-to-rvalue (4.1), array-topointer
(4.2), and function-to-pointer (4.3) standard conversions are
performed on the second and third operands, and
one of the following shall hold:
- The second or the third operand (but not both) is a throw-expression
(15.1); the result is of the type of the other
and is an rvalue.
- Both the second and the third operands have type void; the result is
of type void and is an rvalue. [ Note: this
includes the case where both operands are throw-expressions. -end
note ]
3 Otherwise, if the second and third operand have different types, and
either has (possibly cv-qualified) class type, an
attempt is made to convert each of those operands to the type of the
other. The process for determining whether an
operand expression E1 of type T1 can be converted to match an operand
expression E2 of type T2 is defined as follows:
- If E2 is an lvalue: E1 can be converted to match E2 if E1 can be
implicitly converted (clause 4) to the type "lvalue
reference to T2", subject to the constraint that in the conversion the
reference must bind directly (8.5.3) to E1.
- If E2 is an rvalue, or if the conversion above cannot be done:
- if E1 and E2 have class type, and the underlying class types are the
same or one is a base class of the other: E1
can be converted to match E2 if the class of T2 is the same type as,
or a base class of, the class of T1, and the
cv-qualification of T2 is the same cv-qualification as, or a greater
cv-qualification than, the cv-qualification
of T1. If the conversion is applied, E1 is changed to an rvalue of
type T2 by copy-initializing a temporary of
type T2 from E1 and using that temporary as the converted operand.
- Otherwise (i.e., if E1 or E2 has a nonclass type, or if they both
have class types but the underlying classes are
not either the same or one a base class of the other): E1 can be
converted to match E2 if E1 can be implicitly
converted to the type that expression E2 would have if E2 were
converted to an rvalue (or the type it has, if
E2 is an rvalue).
Using this process, it is determined whether the second operand can be
converted to match the third operand, and
whether the third operand can be converted to match the second
operand. If both can be converted, or one can be
converted but the conversion is ambiguous, the program is ill-formed.
If neither can be converted, the operands
are left unchanged and further checking is performed as described
below. If exactly one conversion is possible,
that conversion is applied to the chosen operand and the converted
operand is used in place of the original operand
for the remainder of this section.
4 If the second and third operands are lvalues and have the same type,
the result is of that type and is an lvalue and it is a
bit-field if the second or the third operand is a bit-field, or if
both are bit-fields.
5 Otherwise, the result is an rvalue. If the second and third operands
do not have the same type, and either has (possibly
cv-qualified) class type, overload resolution is used to determine the
conversions (if any) to be applied to the operands
(13.3.1.2, 13.6). If the overload resolution fails, the program is ill-
formed. Otherwise, the conversions thus determined
are applied, and the converted operands are used in place of the
original operands for the remainder of this section.
6 Lvalue-to-rvalue (4.1), array-to-pointer (4.2), and function-to-
pointer (4.3) standard conversions are performed on the
second and third operands. After those conversions, one of the
following shall hold:

- The second and third operands have the same type; the result is of
that type. If the operands have class type, the
result is an rvalue temporary of the result type, which is copy-
initialized from either the second operand or the
third operand depending on the value of the first operand.
- The second and third operands have arithmetic or enumeration type;
the usual arithmetic conversions are performed
to bring them to a common type, and the result is of that type.
- The second and third operands have pointer type, or one has pointer
type and the other is a null pointer constant;
pointer conversions (4.10) and qualification conversions (4.4) are
performed to bring them to their composite
pointer type (5.9). The result is of the composite pointer type.
- The second and third operands have pointer to member type, or one
has pointer to member type and the other is a
null pointer constant; pointer to member conversions (4.11) and
qualification conversions (4.4) are performed to
bring them to a common type, whose cv-qualification shall match the cv-
qualification of either the second or the
third operand. The result is of the common type.
 
P

Peter Lee

What's _T? Post your real code.

_T is a macro in VC6, it result "efg" or L"efg" depend on _UNICODE
defined or not )
in my case, _T("efg") is equal to "efg"

#include <stdio.h>

class MyClass
{
public:
MyClass(const char* p)
{
printf("ctor p=%s\n", p);
}

operator const char*() const
{
printf("IN operator const char*() const\n");
return "abcd";
}
};


const char* MyTest()
{
printf("IN const char* MyTest()\n");

return "efg";
}


int main()
{

bool b = false;
MyClass s1( "obj1" );

const char* lp = NULL;
lp = (b ? s1 : MyTest()); // return "abcd" !? it create a temp
object

// if i write the following way, lp will be "efg"

// if (b)
// {
// lp = s1;
// }
// else
// {
// lp = MyTest();
// }

printf("lp=%s\n", lp);


return 0;
}

result in VC6.0

ctor p=obj1
IN const char* MyTest()
ctor p=efg
IN operator const char*() const
lp=abcd
 
J

James Kanze

what's the correct behaver about the following code ? ( C++
standard )
I got a very strange result....
class MyClass
{
public:
MyClass(const char* p)
{
printf("ctor p=%s\n", p);
}

operator const char*() const
{
printf("IN operator const char*() const\n");
return "abcd";
}
};
const char* MyTest()
{
printf("IN const char* MyTest()\n");
return _T("efg");
}
int test1(int v)
{

bool b = false;
MyClass s1( "obj1" );
const char* lp = NULL;
lp = (b ? s1 : MyTest()); // lp will be "abcd" !!! strange!?, it
create a temp object !?
// if write the follwing way lp will be "efg"
// if (b)
// {
// lp = s1;
// }
// else
// {
// lp = MyTest();
// }
printf("lp=%s\n", lp);
return 0;
}
// test in VC6.0
ctor p=obj1
IN const char* MyTest()
ctor p=efg
IN operator const char*() const
lp=abcd

That's a very out of date compiler, but I get the same results
with VC8. Both Sun CC and g++ give an error on the ?: operator,
however, and I'm pretty sure that this is what the standard
requires: the rules for type matching in a?b:c start with:

[Cases where b or c have type void elided...]

Otherwise, if the second and third operand have
different types, and either has (possibly cv-qualified)
class type, an attempt is made to convert each of those
operands to the type of the other. The process for
determining whether an operand expression E1 of type T1
can be converted to match an operand expression E2 of
type T2 is defined as follows:

-- If E2 is an lvalue: E1 can be converted to match E2
if E1 can be implicitly converted (clause 4) to the
type "lvalue reference to T2", subject to the
constraint that in the conversion the reference must
bind directly (8.5.3) to E1.

[This should fail, because the result of the
conversion will not bind directly the char const*
which corresponds to E1 in one case, an because the
char const* is not an lvalue in the other.]

-- If E2 is an rvalue, or if the conversion above
cannot be done:

-- if E1 and E2 have class type, [... obviously not
relevant, since char const* doesn't have class
type].

-- Otherwise (i.e., if E1 or E2 has a nonclass
type, or if they both have class types but the
underlying classes are not either the same or
one a base class of the other): E1 can be
converted to match E2 if E1 can be implicitly
converted to the type that expression E2 would
have if E2 were converted to an rvalue (or the
type it has, if E2 is an rvalue).

[This one applies in double, since char const*
can be converted to a MyClass, and MyClass can
be converted to a char const*.]

Using this process, it is determined whether the second
operand can be converted to match the third operand, and
whether the third operand can be converted to match the
second operand. If *both* *can* *be* *converted*
[emphasis added], or one can be converted but the
conversion is ambiguous, the program is ill-formed. If
neither can be converted, the operands are left
unchanged and further checking is performed as described
below. If exactly one conversion is possible, that
conversion is applied to the chosen operand and the
converted operand is used in place of the original
operand for the remainder of this section.

Since both can be converted, the program is ill-formed (and we
don't go any further). I suspect that VC++ has overlooked the
constraint in the first point---that the result of the
conversion must bind directly the the expression. (In practice,
I think this constraint means that only conversions to
references can be considered, but I don't find it really that
clear.)
 
A

antonov84

Since both can be converted, the program is ill-formed (and we
don't go any further). I suspect that VC++ has overlooked the
constraint in the first point---that the result of the
conversion must bind directly the the expression. (In practice,
I think this constraint means that only conversions to
references can be considered, but I don't find it really that
clear.)

Sir, you explain so well, maybe you'll shed the light why putting

lp = ( !b ? MyTest() : s1 );

instead of

lp = ( b ? s1 : MyTest());

will bring forth "efg" instead of "abcd" on the VS 8.0 compiler? All
the rules with E1, E2 seem to imply symmetric evaluation, or...
 
J

James Kanze

Sir, you explain so well, maybe you'll shed the light why putting
lp = ( !b ? MyTest() : s1 );
instead of
lp = ( b ? s1 : MyTest());
will bring forth "efg" instead of "abcd" on the VS 8.0 compiler? All
the rules with E1, E2 seem to imply symmetric evaluation, or...

Compiler error. There's no other explination; as you say, the
rules require symmetric evaluation.

What is probably happening is that the compiler tries one way
first, and uses it if it works, rather than then trying the
second, and declaring an error if both work. In theory, not a
serious error, since it doesn't affect any legal program, but in
practice, rather a bummer, because it means that your tests may
work although there is an error in the code. And of course, no
one is so perfect that they never make an error in their code.
(Of course, no one would seriously suggest that code is correct
just because it passes all of the tests. But this sort of error
is rather subtle, and pretty hard to catch in code review as
well.)
 

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
474,202
Messages
2,571,057
Members
47,661
Latest member
FloridaHan

Latest Threads

Top