Slicing with ?: operator - expected?


V

Victor Bazarov

Hello All,

Here is the code:

class Foo {
public:
virtual void bar() const = 0;
};

#include <ostream>
#include <iostream>

class Foo1 : public Foo {
void bar() const { std::cout << "Foo1::bar\n"; }
};

class Foo2 : public Foo {
void bar() const { std::cout << "Foo2::bar\n"; }
};

void foo(const Foo* pFoo)
{
(pFoo ? *pFoo : Foo1()).bar(); // line 19 ************
}

void blah(const Foo& rFoo)
{
foo(&rFoo);
}

int main()
{
blah(Foo2());
}

Line 19 is the line in question. Could you please interpret it for me?
It seems to skip the virtual function dispatch, and attempt to call
the pure function. The common type of *pFoo and Foo1() is 'class Foo',
and the compiler seems to resolve the 'bar' statically, without the use
of virtual function mechanism, as if there is an instance of class Foo
/sliced/ from both original objects. Is that supposed to happen?
Standard chapter and verse would be helpful.

To avoid a pure function call I can implement the base class' member
function 'bar'. The problem is that in that case I get the dynamic
dispatch I need (I expected Foo2.bar() to be called), I am getting the
base class. Is that what *should* happen?

Thanks!

V
 
Ad

Advertisements

B

Bart van Ingen Schenau

Victor said:
Hello All,

Here is the code:

class Foo {
public:
virtual void bar() const = 0;
};

#include <ostream>
#include <iostream>

class Foo1 : public Foo {
void bar() const { std::cout << "Foo1::bar\n"; }
};

class Foo2 : public Foo {
void bar() const { std::cout << "Foo2::bar\n"; }
};

void foo(const Foo* pFoo)
{
(pFoo ? *pFoo : Foo1()).bar(); // line 19 ************
}

void blah(const Foo& rFoo)
{
foo(&rFoo);
}

int main()
{
blah(Foo2());
}

Line 19 is the line in question. Could you please interpret it for me?
It seems to skip the virtual function dispatch, and attempt to call
the pure function. The common type of *pFoo and Foo1() is 'class
Foo', and the compiler seems to resolve the 'bar' statically, without
the use of virtual function mechanism, as if there is an instance of
class Foo /sliced/ from both original objects. Is that supposed to
happen?

Yes, that is supposed to happen.
The conditional operator (?:) is defined to only yield rvalues, and never a
reference.
This means that objects of class-type indeed get sliced by the ?: operator.
Standard chapter and verse would be helpful.

The behaviour of the conditional operator is described in section 5.16
[expr.cond], where especially the last paragraph is of relevance here.
Thanks!

V

Bart v Ingen Schenau
 
M

Michael DOUBEZ

Hello All,

Here is the code:

    class Foo {
    public:
       virtual void bar() const = 0;
    };

    #include <ostream>
    #include <iostream>

    class Foo1 : public Foo {
       void bar() const { std::cout << "Foo1::bar\n"; }
    };

    class Foo2 : public Foo {
       void bar() const { std::cout << "Foo2::bar\n"; }
    };

    void foo(const Foo* pFoo)
    {
       (pFoo ? *pFoo : Foo1()).bar(); // line 19 ************
    }

    void blah(const Foo& rFoo)
    {
       foo(&rFoo);
    }

    int main()
    {
       blah(Foo2());
    }

Line 19 is the line in question.  Could you please interpret it for me?
  It seems to skip the virtual function dispatch, and attempt to call
the pure function.  The common type of *pFoo and Foo1() is 'class Foo',
and the compiler seems to resolve the 'bar' statically, without the use
of virtual function mechanism, as if there is an instance of class Foo
/sliced/ from both original objects.  Is that supposed to happen?
Standard chapter and verse would be helpful.

My gcc (4.3.3) doesn't even compile it:

fooTest.cc: In function 'void foo(const Foo*)':
fooTest.cc:19: erreur: cannot allocate an object of abstract type
'const Foo'
fooTest.cc:1: note: because the following virtual functions are pure
within 'const Foo':
fooTest.cc:3: note: virtual void Foo::bar() const
fooTest.cc:19: erreur: cannot allocate an object of abstract type
'Foo'
fooTest.cc:1: note: since type 'Foo' has pure virtual functions

It seems the ?: tries to make a copy but the following is accepted:

void foo(const Foo* pFoo)
{
const Foo1 foo1;
(pFoo ? *pFoo : foo1).bar();
}
To avoid a pure function call I can implement the base class' member
function 'bar'.  The problem is that in that case I get the dynamic
dispatch I need (I expected Foo2.bar() to be called), I am getting the
base class.  Is that what *should* happen?

The ?: seems to return a const Foo and not a const Foo& as might be
expected.
gcc 4.3.3 seems to be right because none of the rules of §5.16/3
matches your case.

Note: in n3242 a line reads "If E2 is an xvalue |...]" *xvalue* must
be a typo. Does anyone know if it has been fixed in the new standard ?
 
V

Victor Bazarov

Yes, that is supposed to happen.
The conditional operator (?:) is defined to only yield rvalues, and never a
reference.

I don't think it's correct. If both expressions after the '?' are
lvalues of the same type, the resulting expression should be an lvalue.
Consider:

int x, y; // zero-initialized
int& getx() { return x; }
int& gety() { return y; }

void increment(int& i) { ++i; }

int main(int argc, char **argv)
{
increment(argc % 2 ? getx() : gety());
}

Or do you think it shouldn't compile?
This means that objects of class-type indeed get sliced by the ?: operator.
Standard chapter and verse would be helpful.

The behaviour of the conditional operator is described in section 5.16
[expr.cond], where especially the last paragraph is of relevance here.
Thanks!

V

Bart v Ingen Schenau

V
 
J

Joe Greer

void foo(const Foo* pFoo)
{
(pFoo ? *pFoo : Foo1()).bar(); // line 19 ************
}

I wonder if it isn't the *pFoo that is doing the slicing since we are
dealing with a type const Foo* in the argument list.

joe
 
Ad

Advertisements

B

Balog Pal

Victor Bazarov said:

That is inaccurate for general. But in this case Foo1() is rvalue, so ?:
will provide rvalue.
The behaviour of the conditional operator is described in section 5.16
[expr.cond], where especially the last paragraph is of relevance here.

That says:

"If the conversion is applied, E1 is changed to an rvalue of type T2 that
still refers to the original source class object (or the appropriate
subobject thereof). [Note: that is, no copy is made. ]"

What sounds weird to me, but still implies the observed behavior.
 
P

Paul

Victor Bazarov said:
Hello All,

Here is the code:

class Foo {
public:
virtual void bar() const = 0;
};

#include <ostream>
#include <iostream>

class Foo1 : public Foo {
void bar() const { std::cout << "Foo1::bar\n"; }
};

class Foo2 : public Foo {
void bar() const { std::cout << "Foo2::bar\n"; }
};

void foo(const Foo* pFoo)
{
(pFoo ? *pFoo : Foo1()).bar(); // line 19 ************

What object is the argument pFoo pointing to?
}

void blah(const Foo& rFoo)
{
foo(&rFoo);
Is a temporary reference to a Foo on the call stack?
I think this is the case and there is no Foo2 object.

}

int main()
{
blah(Foo2());

What is the lifespam of the temporary?
}

Line 19 is the line in question. Could you please interpret it for me? It
seems to skip the virtual function dispatch, and attempt to call the pure
function. The common type of *pFoo and Foo1() is 'class Foo', and the
compiler seems to resolve the 'bar' statically, without the use of virtual
function mechanism, as if there is an instance of class Foo /sliced/ from
both original objects. Is that supposed to happen? Standard chapter and
verse would be helpful.

To avoid a pure function call I can implement the base class' member
function 'bar'. The problem is that in that case I get the dynamic
dispatch I need (I expected Foo2.bar() to be called), I am getting the
base class. Is that what *should* happen?
Havent debugged it but maybe it has something to do with the lifespam of the
temporary. Have you tried it using a non temporary object?
 
V

Victor Bazarov

I wonder if it isn't the *pFoo that is doing the slicing since we are
dealing with a type const Foo* in the argument list.

Doing slicing where? On its way into the 'foo' function? Certainly
not. If I rewrite the ?: with 'if-else' syntax, I get the dynamic dispatch.

V
 
J

Joe Greer

Doing slicing where? On its way into the 'foo' function? Certainly
not. If I rewrite the ?: with 'if-else' syntax, I get the dynamic
dispatch.

V

[expr.cond]/3 applies in the case of a condition expression whereas it
doesn't for the if-else stmt. The text is:

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 “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
that still refers to the original source class object (or the
appropriate subobject thereof). [Note: that is, no copy is
made. ]

The way I read it is that since Foo1() is convertible to Foo, but not the
otherway around, Foo1() will be converted/sliced to Foo then the bar()
function will be called.

joe
 
Ad

Advertisements

V

Victor Bazarov

Doing slicing where? On its way into the 'foo' function? Certainly
not. If I rewrite the ?: with 'if-else' syntax, I get the dynamic
dispatch.

V

[expr.cond]/3 applies in the case of a condition expression whereas it
doesn't for the if-else stmt. The text is:

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 “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
that still refers to the original source class object (or the
appropriate subobject thereof). [Note: that is, no copy is
made. ]

The way I read it is that since Foo1() is convertible to Foo, but not the
otherway around, Foo1() will be converted/sliced to Foo then the bar()
function will be called.

I think I get what you're saying. In this particular case, since 'pFoo'
was not null, the second part (E2) wasn't even evaluated. The main
driving thing in your analysis is that the common type is 'const Foo'
(and not 'const Foo&' like one compiler used to think). And it really
comes from the definition of 'operator*' (earlier in the chapter 5).

V
 
Ad

Advertisements


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

Top