Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[over.match.funcs.general] p5 user-defined conversions sequence are not considered for object parameter CWG2557 #5364

Open
xmh0511 opened this issue Mar 25, 2022 · 24 comments
Labels
cwg Issue must be reviewed by CWG.

Comments

@xmh0511
Copy link
Contributor

xmh0511 commented Mar 25, 2022

[over.match.funcs.general] p5 says

The implicit object parameter, however, retains its identity since no user-defined conversions can be applied to achieve a type match with it.

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:

struct A{
   void show(){}
   static void fun(){}
};
struct B{
    using type = A;
    operator A(){
      return A{};
    }
};
B bobj;
bobj.type::show();  // non-static member function  // #1
bobj.type::fun();  // static member function  #2

Even though [class.mfct.non.static] p2 says

If a non-static member function of a class X is called for an object that is not of type X, or of a type derived from X, the behavior is undefined.

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

For static member functions, the implicit object parameter is considered to match any object (since if the function is selected, the object is discarded).

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

User-defined conversion sequences are not considered if the target is the (implicit) object parameter of a member function

[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.

@jensmaurer
Copy link
Member

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].

@jensmaurer
Copy link
Member

jensmaurer commented Mar 25, 2022

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.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 25, 2022

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?

@jensmaurer
Copy link
Member

jensmaurer commented Mar 25, 2022

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;
}

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 25, 2022

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.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 25, 2022

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.

@languagelawyer
Copy link
Contributor

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.

@languagelawyer
Copy link
Contributor

Plus there is https://eel.is/c++draft/class.access.base#6

@jensmaurer
Copy link
Member

CWG2557

@jensmaurer jensmaurer changed the title [over.match.funcs.general] p5 user-defined conversions sequence are not considered for object parameter [over.match.funcs.general] p5 user-defined conversions sequence are not considered for object parameter CWG2557 Mar 25, 2022
@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 25, 2022

@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.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Mar 25, 2022

@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?

@jensmaurer
Copy link
Member

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
Copy link
Contributor

opensdh commented Apr 6, 2022

@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.

@jensmaurer
Copy link
Member

@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.

@opensdh
Copy link
Contributor

opensdh commented Apr 6, 2022

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).

@jensmaurer
Copy link
Member

jensmaurer commented Apr 6, 2022

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?

@jensmaurer
Copy link
Member

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."

@opensdh
Copy link
Contributor

opensdh commented Apr 6, 2022

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.

@jensmaurer
Copy link
Member

Amended CWG2557 accordingly.

@opensdh
Copy link
Contributor

opensdh commented Apr 6, 2022

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.

@jensmaurer
Copy link
Member

Good point: CWG2557 is updated.

@xmh0511
Copy link
Contributor Author

xmh0511 commented Apr 7, 2022

@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 #4731 to expose the subject of that issue.

@jensmaurer
Copy link
Member

It seems we should fix that as a drive-by, plus add the example. CWG2557 is updated.

@jensmaurer jensmaurer added the cwg Issue must be reviewed by CWG. label Apr 21, 2022
@xmh0511
Copy link
Contributor Author

xmh0511 commented Jan 10, 2023

@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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
cwg Issue must be reviewed by CWG.
Projects
None yet
Development

No branches or pull requests

4 participants