C++ Logo

std-discussion

Advanced search

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

From: Tom Honermann <tom_at_[hidden]>
Date: Tue, 24 Sep 2019 13:09:01 -0400
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]
> <mailto: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]
> <mailto:std-discussion_at_[hidden]>>
> Date: 9/24/19 05:21 (GMT-05:00)
> To: std-discussion_at_[hidden]
> <mailto:std-discussion_at_[hidden]>
> Cc: Andrew Schepler <aschepler_at_[hidden]
> <mailto: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] <mailto: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] <mailto: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]
> <mailto: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]
> <mailto: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]
> <mailto:Std-Discussion_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
> --
> Std-Discussion mailing list
> Std-Discussion_at_[hidden]
> <mailto: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]
> <mailto:Std-Discussion_at_[hidden]>
> https://lists.isocpp.org/mailman/listinfo.cgi/std-discussion
>
>
>
> --
> /Brian Bi/
>


Received on 2019-09-24 12:11:12