C++ Logo

std-discussion

Advanced search

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

From: Brian Bi <bbi5291_at_[hidden]>
Date: Tue, 24 Sep 2019 13:25:49 -0500
It would seem that MSVC and ICC also give `const S`.

https://www.godbolt.org/z/ejen4_

On Tue, Sep 24, 2019 at 12:09 PM Tom Honermann <tom_at_[hidden]> wrote:

> On 9/24/19 12:14 PM, Brian Bi via Std-Discussion wrote:
>
> Thanks for the responses, everyone.
>
> Can someone remind me which the "big 4" compilers are? Did they all
> consider the result type to be `const S`?
>
> Such references generally refer to gcc, Clang, MSVC, and EDG derived
> compilers such as Intel's ICC
>
> You can test all of them (as well as some others) at
> https://www.godbolt.org.
>
> Tom.
>
>
> (It seems that it is time to open a core issue and I would like to mention
> this as part of the report.)
>
> On Tue, Sep 24, 2019 at 4:42 AM Krystian Stasiowski via Std-Discussion <
> std-discussion_at_[hidden]> wrote:
>
>> Oops. Here it is again:
>>
>> I'm going to break it down, word by word, lets see how this goes. Please,
>> fact check this!
>>
>> #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://eel.is/c++draft/expr.cond#4 applies because S{} and s have
>> differing types (S and const S). Therefore, we must form a implicit
>> conversion sequence between the two:
>>
>> Given E1 is S{}, E2 is s, T1 is S and T2 is const S:
>> - E2 is an lvalue, therefore the target type is const S&
>> http://eel.is/c++draft/expr.cond#4.1. The target is a reference type, so
>> we follow the rules of http://eel.is/c++draft/over.ics.ref
>> - S{} binds directly to the reference. The conversion is an identity
>> conversion. http://eel.is/c++draft/over.ics.ref#1
>> - http://eel.is/c++draft/expr.cond#4.1 mandates that the reference
>> must bind directly to the glvalue. *This is the first point of
>> divergence*: If this is indeed referring to the operand itself, in this
>> case S{}, without the temporary materialization conversion applied, then
>> this sub clause does not apply, and instead the rules of
>> http://eel.is/c++draft/expr.cond#4.3 apply, which state that in this
>> case, since T2 is al least as cv-qualified as T1, the target type is T2,
>> and a conversion sequence is formed; the result is the same for both.
>> However, this would stop us dead in our tracks, and make the program
>> ill-formed. *I don't believe this is intended. *We will assume this is
>> unintentional, and throw in the possibility that the conversion sequence
>> cannot be formed, assuming that http://eel.is/c++draft/expr.cond#4.1 really
>> does require E1 to be a glvalue without the temporary materialization
>> conversion applied.
>>
>> Now, the calculation is complete. *The conversion sequence for S{} to s
>> is either not able to be formed, or is an identity conversion.*
>>
>> Now, for the second sequence:
>> Given E1 is s, E2 is S{}, T1 is const S and T2 is S:
>> - E2 is a prvalue so http://eel.is/c++draft/expr.cond#4.3 applies. T2 is
>> less cv-qualified than T1, so 4.3.1 does not apply. T2 is not a base class,
>> so 4.3.2 does not apply. Therefore, the target type is the type of E2 after
>> the lvalue-to-rvalue, function-to-pointer, and array to pointer conversions
>> are applied - these have no effect. The target type is S.
>> - The target type is not a reference, so
>> http://eel.is/c++draft/over.best.ics#6 applies. The conversion sequence
>> is the one that converts E2 to a prvalue of of target type, S
>> http://eel.is/c++draft/over.best.ics#6.sentence-2. Top level
>> cv-qualifiers are ignored
>> http://eel.is/c++draft/over.best.ics#6.sentence-4. As they are ignored,
>> and they both have the same class type, the conversion is an identity
>> conversion http://eel.is/c++draft/over.best.ics#6.sentence-7
>>
>> Side note: The standard kinda disagrees with itself here, it first states
>> "The implicit conversion sequence is the one required to convert the
>> argument expression to a prvalue of the type of the parameter." (
>> http://eel.is/c++draft/over.best.ics#6.sentence-2) and also says "When
>> the parameter has a class type and the argument expression has the same
>> type, the implicit conversion sequence is an identity conversion.". We will
>> assume that it means the later since it is more constrained, and therefore
>> does not include the lvalue-to-rvalue conversion. This does not change the
>> later result, as a lvalue-to-rvalue conversion would not discard the
>> cv-qualifiers https://eel.is/c++draft/conv.lval#1
>>
>> This calculation is complete. *The conversion sequence for s to S{} is
>> an identity conversion.*
>>
>> Here are our two options now:
>> 1. The conversion sequence for S{} to s cannot be formed, so the
>> conversion sequence for s to S{} is applied to s
>> http://eel.is/c++draft/expr.cond#4.sentence-7. It has no effect on value
>> category or type, as no conversion is performed.
>> 2. Both conversion sequences are formed, making the program ill-formed.
>> http://eel.is/c++draft/expr.cond#4.sentence-5
>>
>> Since option 2 makes the program ill-formed, and the big 4 all agree that
>> this is a well-formed construct, we can assume that it does mean that in
>> the case of https://eel.is/c++draft/expr.cond#4.1 E1 must be a
>> glvalue, the reference must bind directly to it, and
>> http://eel.is/c++draft/expr.cond#4.3 will not kick in to form that
>> implicit conversion sequence.
>>
>> From this point on, we will refer to the converted operands as CE1, and
>> CE2 (even though only an identity conversion was done)
>>
>> Now for option 1:
>> - EC1 and EC2 do not have the same value category, nor the same type, so
>> http://eel.is/c++draft/expr.cond#5 does not apply. This means the
>> result is a prvalue. http://eel.is/c++draft/expr.cond#6
>> - Since the types of EC1 and EC2 differ, and both are class types,
>> http://eel.is/c++draft/expr.cond#6 say "overload resolution is used to
>> determine the conversions (if any) to be applied to the operands" and
>> cross-references http://eel.is/c++draft/over.match.oper#3.3 and
>> http://eel.is/c++draft/over.match.oper.
>>
>> * This is the second point of divergence*, built in candidates are
>> defined for the conditional operator, but since it is not a binary operator
>> nor a unary operator, the entirety of
>> http://eel.is/c++draft/over.match.oper#3 does not apply. The note
>> http://eel.is/c++draft/over.match.oper#1.sentence-3 does state that the
>> rules in the sub-clause are used to determine the conversions applied to
>> the operands, but there is no such clause in
>> http://eel.is/c++draft/over.match.oper that describes this behavior in
>> our case (nor even a semblance of one).
>> http://eel.is/c++draft/over.match.oper#2 says that overload resolution
>> is performed to determine which built-in operator will be used. However,
>> the only candidates defined (http://eel.is/c++draft/over.built#27 and
>> http://eel.is/c++draft/over.built#28) do not include class types, and
>> since S cannot be converted to an arithmetic type, pointer type, pointer to
>> member type or scoped enumeration type, none of these will be selected. *This
>> is definitely not intended*, since it would only work for class types
>> convertible to arithmetic, pointer, pointer to member and scoped
>> enumeration types.
>>
>>
>>
>> * WARNING: HERE BE SPECULATION LAND This leaves us with two options, the
>> construct is ill-formed, or we can assume make an educated guess based on
>> the text.* Since all compilers tested accept this, we can assume that
>> there is some invented built in candidate T operator?:(bool, T, T); Where
>> T is determined as follows.
>> - If both operands are of the same class type (ignoring
>> cv-qualification), the T is the cv-combined type of the types of both
>> operands
>> - Otherwise, if only one operand is of (possibly cv-qualified) class type
>> U, T is U
>> - Otherwise, if both are class types, an attempt is made to form
>> conversion sequences between the two. If both are formed, or none are, the
>> program is ill-formed. Otherwise, T is the target type of the conversion
>> sequence that was formed.
>>
>> (This is by no means correct, just a rough outline based on my
>> observations of the behavior of clang and gcc. This *should* replicated
>> the behavior, but I have not tested all possible cases)
>>
>> This would result in overload resolution selecting this invented operator
>> function, and the operands would be converted to the types of the
>> parameters http://eel.is/c++draft/over.match.oper#10. When applied to
>> our case, the converted operands are of type S and const S; the cv-combined
>> type of these is const S, leading both operands being converted to const S.
>> Now that the types match exactly, http://eel.is/c++draft/expr.cond#7 applies,
>> and the lvalue-to-rvalue, array-to-pointer, and function-to-pointer
>> standard conversions are performed on the second and third operands, and
>> the result is of type const S, which is what clang and gcc report.
>>
>>
>> * Here is the final tally of possible behaviors: 1. Both conversion
>> sequences are formed, making the program ill-formed (I don't consider this
>> an option, I just included it here for the sake of being complete)*
>> *2. Only the s to S{} sequence is formed, but no built in candidate
>> exists for T operator?:(T, T) where T is a class type. Overload resolution
>> fails, and the program is ill formed.*
>> *3. Only the s to S{} sequence is formed, and there exists a candidate
>> function T operator?:(T, T) where T is const S. Overload resolution is
>> successful, and both operands are converted to const S. The expression is a
>> prvalue of type const S.*
>>
>> Option 3 is the one that best illustrates the behavior of the major
>> compilers, so even though it uses some extrapolated wording, I believe this
>> is what is actually happening. After spending 2.5 hours researching this
>> and tracing it though the standard, I think its safe to say that this
>> wording is defective. I think that the fact that top level cv-qualifiers
>> are ignored when forming a implicit conversion sequence was overlooked when
>> this was written, or perhaps the wording was changed, and this was just
>> never updated.
>>
>> -------- Original message --------
>> From: Andrew Schepler via Std-Discussion <std-discussion_at_[hidden]>
>>
>> Date: 9/24/19 05:21 (GMT-05:00)
>> To: std-discussion_at_[hidden]
>> Cc: Andrew Schepler <aschepler_at_[hidden]>
>> Subject: Re: [std-discussion] Conditional operator with const lvalue and
>> non-const prvalue
>>
>> It looks like you accidentally sent that last message to just me, and not
>> the mailing list:
>>
>> On Tue, Sep 24, 2019 at 12:29 AM Krystian Stasiowski <
>> sdkrystian_at_[hidden]> wrote:
>>
>>> I'm going to break it down, word by word, lets see how this goes.
>>> Please, fact check this!
>>>
>>> #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://eel.is/c++draft/expr.cond#4 applies because S{} and s have
>>> differing types (S and const S). Therefore, we must form a implicit
>>> conversion sequence between the two:
>>>
>>> Given E1 is S{}, E2 is s, T1 is S and T2 is const S:
>>> - E2 is an lvalue, therefore the target type is const S&
>>> http://eel.is/c++draft/expr.cond#4.1. The target is a reference type,
>>> so we follow the rules of http://eel.is/c++draft/over.ics.ref
>>> - S{} binds directly to the reference. The conversion is an identity
>>> conversion. http://eel.is/c++draft/over.ics.ref#1
>>> - http://eel.is/c++draft/expr.cond#4.1 mandates that the reference
>>> must bind directly to the glvalue. *This is the first point of
>>> divergence*: If this is indeed referring to the operand itself, in this
>>> case S{}, without the temporary materialization conversion applied, then
>>> this sub clause does not apply, and instead the rules of
>>> http://eel.is/c++draft/expr.cond#4.3 apply, which state that in this
>>> case, since T2 is al least as cv-qualified as T1, the target type is T2,
>>> and a conversion sequence is formed; the result is the same for both.
>>> However, this would stop us dead in our tracks, and make the program
>>> ill-formed. *I don't believe this is intended. *We will assume this is
>>> unintentional, and throw in the possibility that the conversion sequence
>>> cannot be formed, assuming that http://eel.is/c++draft/expr.cond#4.1 really
>>> does require E1 to be a glvalue without the temporary materialization
>>> conversion applied.
>>>
>>> Now, the calculation is complete. *The conversion sequence for S{} to s
>>> is either not able to be formed, or is an identity conversion.*
>>>
>>> Now, for the second sequence:
>>> Given E1 is s, E2 is S{}, T1 is const S and T2 is S:
>>> - E2 is a prvalue so http://eel.is/c++draft/expr.cond#4.3 applies. T2
>>> is less cv-qualified than T1, so 4.3.1 does not apply. T2 is not a base
>>> class, so 4.3.2 does not apply. Therefore, the target type is the type of
>>> E2 after the lvalue-to-rvalue, function-to-pointer, and array to pointer
>>> conversions are applied - these have no effect. The target type is S.
>>> - The target type is not a reference, so
>>> http://eel.is/c++draft/over.best.ics#6 applies. The conversion sequence
>>> is the one that converts E2 to a prvalue of of target type, S
>>> http://eel.is/c++draft/over.best.ics#6.sentence-2. Top level
>>> cv-qualifiers are ignored
>>> http://eel.is/c++draft/over.best.ics#6.sentence-4. As they are ignored,
>>> and they both have the same class type, the conversion is an identity
>>> conversion http://eel.is/c++draft/over.best.ics#6.sentence-7
>>>
>>> Side note: The standard kinda disagrees with itself here, it first
>>> states "The implicit conversion sequence is the one required to convert the
>>> argument expression to a prvalue of the type of the parameter." (
>>> http://eel.is/c++draft/over.best.ics#6.sentence-2) and also says "When
>>> the parameter has a class type and the argument expression has the same
>>> type, the implicit conversion sequence is an identity conversion.". We will
>>> assume that it means the later since it is more constrained, and therefore
>>> does not include the lvalue-to-rvalue conversion. This does not change the
>>> later result, as a lvalue-to-rvalue conversion would not discard the
>>> cv-qualifiers https://eel.is/c++draft/conv.lval#1
>>>
>>> This calculation is complete. *The conversion sequence for s to S{} is
>>> an identity conversion.*
>>>
>>> Here are our two options now:
>>> 1. The conversion sequence for S{} to s cannot be formed, so the
>>> conversion sequence for s to S{} is applied to s
>>> http://eel.is/c++draft/expr.cond#4.sentence-7. It has no effect on
>>> value category or type, as no conversion is performed.
>>> 2. Both conversion sequences are formed, making the program ill-formed.
>>> http://eel.is/c++draft/expr.cond#4.sentence-5
>>>
>>> Since option 2 makes the program ill-formed, and the big 4 all agree
>>> that this is a well-formed construct, we can assume that it does mean that
>>> in the case of https://eel.is/c++draft/expr.cond#4.1 E1 must be a
>>> glvalue, the reference must bind directly to it, and
>>> http://eel.is/c++draft/expr.cond#4.3 will not kick in to form that
>>> implicit conversion sequence.
>>>
>>> From this point on, we will refer to the converted operands as CE1, and
>>> CE2 (even though only an identity conversion was done)
>>>
>>> Now for option 1:
>>> - EC1 and EC2 do not have the same value category, nor the same type,
>>> so http://eel.is/c++draft/expr.cond#5 does not apply. This means the
>>> result is a prvalue. http://eel.is/c++draft/expr.cond#6
>>> - Since the types of EC1 and EC2 differ, and both are class types,
>>> http://eel.is/c++draft/expr.cond#6 say "overload resolution is used to
>>> determine the conversions (if any) to be applied to the operands" and
>>> cross-references http://eel.is/c++draft/over.match.oper#3.3 and
>>> http://eel.is/c++draft/over.match.oper.
>>>
>>> * This is the second point of divergence*, built in candidates are
>>> defined for the conditional operator, but since it is not a binary operator
>>> nor a unary operator, the entirety of
>>> http://eel.is/c++draft/over.match.oper#3 does not apply. The note
>>> http://eel.is/c++draft/over.match.oper#1.sentence-3 does state that the
>>> rules in the sub-clause are used to determine the conversions applied to
>>> the operands, but there is no such clause in
>>> http://eel.is/c++draft/over.match.oper that describes this behavior in
>>> our case (nor even a semblance of one).
>>> http://eel.is/c++draft/over.match.oper#2 says that overload resolution
>>> is performed to determine which built-in operator will be used. However,
>>> the only candidates defined (http://eel.is/c++draft/over.built#27 and
>>> http://eel.is/c++draft/over.built#28) do not include class types, and
>>> since S cannot be converted to an arithmetic type, pointer type, pointer to
>>> member type or scoped enumeration type, none of these will be selected. *This
>>> is definitely not intended*, since it would only work for class types
>>> convertible to arithmetic, pointer, pointer to member and scoped
>>> enumeration types.
>>>
>>>
>>>
>>> * WARNING: HERE BE SPECULATION LAND This leaves us with two options, the
>>> construct is ill-formed, or we can assume make an educated guess based on
>>> the text.* Since all compilers tested accept this, we can assume that
>>> there is some invented built in candidate T operator?:(bool, T, T); Where
>>> T is determined as follows.
>>> - If both operands are of the same class type (ignoring
>>> cv-qualification), the T is the cv-combined type of the types of both
>>> operands
>>> - Otherwise, if only one operand is of (possibly cv-qualified) class
>>> type U, T is U
>>> - Otherwise, if both are class types, an attempt is made to form
>>> conversion sequences between the two. If both are formed, or none are, the
>>> program is ill-formed. Otherwise, T is the target type of the conversion
>>> sequence that was formed.
>>>
>>> (This is by no means correct, just a rough outline based on my
>>> observations of the behavior of clang and gcc. This *should* replicated
>>> the behavior, but I have not tested all possible cases)
>>>
>>> This would result in overload resolution selecting this invented
>>> operator function, and the operands would be converted to the types of
>>> the parameters http://eel.is/c++draft/over.match.oper#10. When applied
>>> to our case, the converted operands are of type S and const S; the
>>> cv-combined type of these is const S, leading both operands being converted
>>> to const S. Now that the types match exactly,
>>> http://eel.is/c++draft/expr.cond#7 applies, and the lvalue-to-rvalue,
>>> array-to-pointer, and function-to-pointer standard conversions are
>>> performed on the second and third operands, and the result is of type const
>>> S, which is what clang and gcc report.
>>>
>>>
>>> * Here is the final tally of possible behaviors: 1. Both conversion
>>> sequences are formed, making the program ill-formed (I don't consider this
>>> an option, I just included it here for the sake of being complete)*
>>> *2. Only the s to S{} sequence is formed, but no built in candidate
>>> exists for T operator?:(T, T) where T is a class type. Overload resolution
>>> fails, and the program is ill formed.*
>>> *3. Only the s to S{} sequence is formed, and there exists a candidate
>>> function T operator?:(T, T) where T is const S. Overload resolution is
>>> successful, and both operands are converted to const S. The expression is a
>>> prvalue of type const S.*
>>>
>>> Option 3 is the one that best illustrates the behavior of the major
>>> compilers, so even though it uses some extrapolated wording, I believe this
>>> is what is actually happening. After spending 2.5 hours researching this
>>> and tracing it though the standard, I think its safe to say that this
>>> wording is defective. I think that the fact that top level cv-qualifiers
>>> are ignored when forming a implicit conversion sequence was overlooked when
>>> this was written, or perhaps the wording was changed, and this was just
>>> never updated.
>>>
>>> On Mon, Sep 23, 2019 at 7:42 PM Andrew Schepler <aschepler_at_[hidden]>
>>> wrote:
>>>
>>>> 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
>>>>
>>>>
>> "Identity conversion" does not mean there is no change in type or value
>> category. It only means that the conversion sequence is associated with
>> category "Identity conversion" for purposes of comparing which implicit
>> conversion sequence is better during overload resolution. In
>> [over.best.ics]/6, "Any difference in top-level cv-qualification is
>> subsumed by the initialization itself and does not constitute a
>> conversion." doesn't mean that the cv-qualifiers of the target type must
>> match the expression type; it means that if the only difference in
>> qualifiers is at the top level, the implicit conversion sequence used in
>> comparisons doesn't include a qualification conversion. In the same
>> paragraph, "When the parameter has a class type and the argument expression
>> has the same type, the implicit conversion sequence is an identity
>> conversion." also doesn't change the target type; it means that even if the
>> actual initialization requires a move/copy constructor, the ICS category is
>> Identity and the rank is Exact Match.
>>
>> So the conversion of "s" to a type related to "S{}" can be from "const S"
>> to "S". If we suppose that the conversion from "S{}" to a type related to
>> "s" cannot be formed, I agree the final type should be "S" and not "const
>> S". Though I still don't see why (4.3) wouldn't apply to the conversion of
>> "S{}" if (4.1) doesn't. Possibly the Standard wording there could be fixed
>> or clarified.
>>
>> I also agree there seems to be some wording missing about exactly when
>> and how overload resolution is involved in semantic analysis of the
>> conditional expression. Something should say that it goes through the same
>> process as unary and binary expressions in [over.match.oper] using the
>> phony name "operator?:", but the only candidate functions are the built-in
>> candidate set. But I think it's intentional that [over.built] does not
>> include any signatures involving class types, because getting past
>> [expr.cond]/5 to the "Otherwise" [expr.cond]/6 where overload resolution
>> gets involved is meant to be just for cases where the types aren't similar
>> enough to use "Exact Match" or "Derived To Base" conversion sequences, and
>> we can't convert either expression to the other's type, so the final option
>> is to use one or two conversion functions to convert each class type
>> operand to a non-class type.
>>
>> Though this is backwards from the normal pattern: for most compound
>> expressions, we can start with [over.match.oper]. At the very start, it
>> says overloading does not apply if no operand has class or enum type, so
>> only [expr] applies. Otherwise, overload resolution determines whether the
>> compound expression is a function call or has the "built-in" meaning in
>> [expr], and if it has the built-in meaning, the selected built-in candidate
>> function determines any conversions to be applied before the semantics of
>> [expr]. For a ternary conditional expression, it only makes sense to start
>> analysis at [expr.cond]. If we pass the "Otherwise" at the start of
>> [expr.cond], then [over.match.oper] applies, forcing the result type to be
>> a non-class type. If that's really how it should be read, I think it would
>> be good to have something mention that exception.
>>
>> (Some of your phrasing seems to imply "since compilers do this, the
>> Standard must be interpreted in a way consistent with that result", and I'd
>> be careful about that reversal. Compiler teams are capable of error, and
>> there might be some common reason multiple implementations use logic that
>> matches the Standard most of the time but happens to give a consistent
>> different result in one corner case not specifically intended. There's also
>> the possibility the Standard has a defect or insufficiently clear wording.
>> And of course common sense can enter into it: if selecting one of two
>> expressions with a type differing only by "const" can ever make the code
>> ill-formed, something is wrong.)
>>
>> --
>> Std-Discussion mailing list
>> Std-Discussion_at_[hidden]
>> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>>
>
>
> --
> *Brian Bi*
>
>
>

-- 
*Brian Bi*

Received on 2019-09-24 13:28:10