C++ Logo

std-discussion

Advanced search

Re: Conditional operator with const lvalue and non-const prvalue

From: Andrew Schepler <aschepler_at_[hidden]>
Date: Mon, 23 Sep 2019 19:42:46 -0400
Yes, the conversion from prvalue S{} to type const S& would bind directly,
but it "is said to bind directly to the initializer expression", and the
initializer expression "S{}" is not a glvalue. A bit confusing, since the
reference "binds to" the lvalue resulting from the temporary
materialization conversion. But I think this is intended, because I think
the point is that a temporary materialization conversion is never applied
to just one of the second or third operands of the conditional. If it did,
automatically destroying the temporary at the end of its lifetime would
depend on more than just the program counter register, causing brand new
complications for compilers, particularly for stack unwinding on exception
propagation.

There certainly is an implicit conversion sequence from an lvalue of type
const S to type S, via the copy constructor. Otherwise we couldn't do:

struct S {};
void f(S) {}
int main() {
    const S s;
    f(s);
}

But in converting "S{}" to a type related to "s", wouldn't
[expr.cond]/(4.3) apply, if the conversion in [expr.cond]/(4.1) is not
possible? Note there have been changes to (4.3) since C++17:

C++17 N4659:
(4.3.1) if T1 and T2 are the same class type (ignoring cv-qualification),
or one is a base class of the other, and T2 is at least as cv-qualified as
T1, the target type is T2,
(4.3.2) otherwise, ...

C++20 latest at https://timsong-cpp.github.io/cppwp/expr.cond :
(4.3.1) if T1 and T2 are the same class type (ignoring cv-qualification)
and T2 is at least as cv-qualified as T1, the target type is T2,
(4.3.2) otherwise, if T2 is a base class of T1, the target type is cv1 T2,
where cv1 denotes the cv-qualifiers of T1,
(4.3.3) otherwise, ...

But I don't see how that change would make any implicit conversion sequence
unavailable. Which would mean both are possible and "b ? S{} : s" is
ill-formed - not desirable. (It might make sense to say if both are
possible and the target types are the same, the expression is a prvalue
with that type; but it doesn't say that.)




On Mon, Sep 23, 2019 at 6:51 PM Krystian Stasiowski via Std-Discussion <
std-discussion_at_[hidden]> wrote:

> Only the last case where the initializer expression is converted does it
> not bind directly http://eel.is/c++draft/dcl.init.ref#5.4
>
> The implicit conversion sequence can be formed from the prvalue to the
> lvalue. Since constness matters for prvalues of class type and is not
> ignored, an implicit conversion sequence cannot be formed from const S to
> S.
>
> I think.
>
> On Mon, Sep 23, 2019 at 11:02 AM Brian Bi via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
>
>> Consider the following:
>>
>> #include <type_traits>
>> struct S {};
>> bool b;
>> int main() {
>> const S s {};
>> static_assert(std::is_same<decltype(b ? S{} : s), const S>::value);
>> }
>>
>> Here <http://coliru.stacked-crooked.com/a/49d1853146cbcdc1> GCC and
>> Clang confirm that the type is correct. But I can't figure out why. My
>> reading of the standard is that the result should be a prvalue of type S,
>> not const S.
>>
>> [expr.cond]/4 applies because the two operand types are not the same (one
>> is S, the other is const S) but at least one is a class type. We must
>> therefore try to form an implicit conversion sequence in each direction. An
>> implicit conversion sequence cannot be formed from the prvalue operand to
>> const S& because (4.1) contains a restriction that the reference must bind
>> directly to a glvalue. In the other direction, we have the identity
>> conversion sequence from the const lvalue operand to S. Thus, /4 seems to
>> tell us that the const lvalue operand must be converted to S, and the
>> result should have type S.
>>
>> Yet I would not expect both GCC and Clang to be wrong here, so I think
>> that I have misunderstood the standard in this case. Surely there must be a
>> reason why the result has type const S, but I can't figure it out.
>>
>> (Even if we assume that the compiler is obligated to perform an
>> lvalue-to-rvalue conversion on the const lvalue operand, resulting in a
>> const prvalue, that still doesn't seem to explain the result. If this were
>> the case, /4 would end with a const prvalue and a non-const prvalue, /5
>> would not apply, and we would get to /6 and the types would still not be
>> the same. The "overload resolution" procedure prescribed there would fail
>> since S can't be converted to any scalar types, making the program
>> ill-formed. This interpretation thus cannot be correct either.)
>>
>> --
>> *Brian Bi*
>> --
>> Std-Discussion mailing list
>> Std-Discussion_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>>
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>

Received on 2019-09-23 18:45:08