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 11:14:36 -0500
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`?

(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 LANDThis 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 LANDThis 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*

Received on 2019-09-24 11:16:58