Open xmh0511 opened 2 years ago
I think the example has nothing to do with user-defined conversions or functions. We'd also expect an ill-formed error if bobj.type::show
were a (static or non-static) data member. Note that A
and B
don't derive from each other.
Thus, the right place to look for a restriction is [expr.ref].
struct A {
static int x;
};
struct B {
using type = A;
};
int y = B().type::x;
@opensdh, I'm not seeing anything that would make this example ill-formed. I think [expr.ref] should contain a check before the if-ladder on the kind of E2.
Thus, the right place to look for a restriction is [expr.ref].
Yes, that could be a more general way instead of concerning only member functions. If we would have that restriction, is it necessary to remain the sentence(user-defined conversion...) in [over.match.funcs.general] p5?
Yes, I think that's still necessary for cases roughly similar to this:
struct B {
void operator+(int) const;
};
struct D : B { };
void operator+(const D&, long);
int main()
{
D() + 0;
}
However, [over.match.oper] p2 will reinterpret the expression to a function call, which depends on what kind of the function is.
For non-member function, it is operator+(D(),0)
. Instead, for member function, the expression will be reinterpretd to D().operator+(0)
, D().operator+(0)
is till governed by [expr.ref] and only member functions can have a implicit object parameter.
There is also another concern: Are user-defined conversion sequences permitted for an explicit object parameter? This is even not mentioned by [over.match.funcs.general] p5 that only talks about implicit object parameter.
I think the restriction for the non-static case has been mentioned a couple of times in other issues, it is https://eel.is/c++draft/expr.prim.id.general#3 Yes, it is not perfect, but replacing «can» with «shall» and «in which the object expression refers to the member's class» with «in which the object expression is of the member's class … type» feels almost editorial.
For the static members case, I'd say it is implementations' bug. It would be natural to extrapolate CWG315 decision from «we don't care about the result of the object expression» to «also, we don't care about its type» if the RHS of [expr.ref] names a static member.
Plus there is https://eel.is/c++draft/class.access.base#6
CWG2557
@languagelawyer [expr.prim.id.general] p3 and [class.access.base] p6 are well applied to non-static member. I wanted to use the code at #1
to expose some issues about "user-defined conversion sequence", however, it is a wrong way according to the aforemtioned rules.
@jensmaurer If "user-defined conversion sequence restriction" is necessary for implicit object parameter, could we place it in [over.best.ics] and replace it with constructive wording? BTW, how about the explicit object parameter?
I think the explicit object parameter wants all conversions it can get; please double-check the paper that introduced it.
Regarding the move to [over.best.ics]: Yes, we probably could do that. I'm not seeing that high on the priority list, given that it works as currently specified (even though it's not pretty).
@opensdh, I'm not seeing anything that would make this example ill-formed. I think [expr.ref] should contain a check before the if-ladder on the kind of E2.
Before P1787R6 it did contain
The id-expression shall name a member of the class or of one of its base classes.
but that doesn't work anymore because of using E::e;
, so we need a more subtle rule.
@opensdh, I see. I think "E2 shall denote a member of the type of E1." in [expr.ref] will fix it, and will sidestep any using E::e
. See CWG2557.
I don't see how changing "shall name a member" to "shall denote a member" allows
enum E {e};
struct X {
using E::e;
};
int f(X x) {return x.e;}
because E::e
is not a member of X
(its declaration in E
was simply found by a using-declaration in X
).
Thanks for the more complete example. I thought you were concerned about the qualified-id in using E::e
itself. Yes, this is a problem. But if we allow this non-member access, maybe we ought to allow the other example, too?
Maybe "If E2 is a qualified-id, the terminal name of its nested-name-specifier shall denote the type of E1 or a base class thereof."
I think that rule makes sense: if you want to refer to A::x
in the example, you can use B::type::x
. The permission to find non-non-static members with .
does not seem to have any need to be extended to arbitrary qualified-ids.
Amended CWG2557 accordingly.
Can we strike the insufficient text from [expr.prim.id.general]/3 now? I don't see how an unqualified-id would ever refer to the wrong class.
Good point: CWG2557 is updated.
@jensmaurer @opensdh
enum E {e};
struct X {
using E::e;
};
int f(X x) {return x.e;}
For this example, only [expr.ref] p6.5 mentions enumerator in a class member access. It says: If E2 is a member enumerator, Is E::e
a member enumerator of X
? It seems that we consider it is not
because E::e is not a member of X
So, how does [expr.ref] specify that x.e
is well-formed? I think [expr.ref] p6.5 might be changed to
If E2 is a ~member~ enumerator and the type of E2 is T, the expression E1.E2 is a prvalue. The type of E1.E2 is T.
Since [basic.lookup.general] p3 says
If any such declaration is a using-declarator whose terminal name ([expr.prim.id.unqual]) is not dependent ([temp.dep.type]), it is replaced by the declarations named by the using-declarator ([namespace.udecl]).
I also think this example is better than the example in https://github.com/cplusplus/draft/issues/4731 to expose the subject of that issue.
It seems we should fix that as a drive-by, plus add the example. CWG2557 is updated.
@jensmaurer @opensdh Another case that would be clarified by the fix is:
struct A{
enum class C{
a = 0
};
};
int main(){
A a;
auto c = a.C::a; //#1
}
For this, I posted the bug file for GCC. However, consider a subtle case
struct A{
enum C{ // unscoped enumeration results in that `a` is a member of class `A`
a = 0
};
};
int main(){
A a;
auto c = a.C::a; //#1
}
Is this well-formed or ill-formed, according to the wording in CWG2557, it is ill-formed since C::a
is a qualified-id for which the terminal name of its nested-name-specifier does not denote A
. Should the second case deserve to be ill-formed or well-formed?
[over.match.funcs.general] p5 says
It sounds like a note. The implicit conversion sequence only considered whether converting an argument to the type of the parameter can form an implicit conversion sequence. So, "retains its identity" is not matter with the reason why user-defined conversions cannot be applied. Consider this example:
Even though [class.mfct.non.static] p2 says
However, it restricts nothing on the conversion sequence that converts the implicit object argument to the type of the object parameter.
It is even worse the status quo is not clear for a static member function. [over.match.funcs.general] p4 says
which bullet can interpret why
#2
is ill-formed? neither [basic.lookup], [expr.ref], nor [over.match.funcs.general]I think we should have a formal rule in a similar manner as [over.best.ics.general] p4, in that subclause, it is a reasonable place to talk about conversion sequence.
Such as
[over.match.funcs.general] p5 can refer to the added rule. In addition, the original [over.match.funcs.general] p5 does not cover explicit object parameter, does it mean user-defined conversion sequence can be considered for it?
For the static member case at
#2
, I haven't found a corresponding rule that can interpret the ill-formed.