https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102609

--- Comment #13 from waffl3x <waffl3x at protonmail dot com> ---
I am finding myself realizing that implementing this as a member function and
turning off member function bits seems to be more difficult than implementing
it as a static function and implementing member function bits will be.
However, I am uncertain on the differences in what is in scope in static member
functions and implicit object member functions are, and whether explicit object
member functions scope is equal to either of them. Are you required to access
member type aliases through the type of the explicit object parameter? Upon
typing this I'm realizing that cases that deduce the explicit object parameter
would need to access it through the template type, so I guess the answer is
yes?

struct S {
    using my_type = int;
    void f0(this S, my_type) {}
    void g0(this S) {
        my_type local;
    }
};

struct S {
    using my_type = int;
    void f1(this S, S::my_type) {}
    void g1(this S) {
        S::my_type local;
    }
};

Of these cases, is f0 or f1 and g0 or g1 correct? I assume the answer is f1 and
g1.
And does the answer change when the type is deduced instead?

struct S {
    using my_type = int;
    template<typename Self>
    void f0(this Self, my_type) {}
    template<typename Self>
    void g0(this Self) {
        my_type local;
    }
};

struct S {
    using my_type = int;
    template<typename Self>
    void f0(this Self, Self::my_type) {}
    template<typename Self>
    void g0(this Self) {
        Self::my_type local;
    }
};

When deduced, f0 or f1 and g0 or g1? I would definitely think f1 and g1 now.

Really, writing the question out I feel like I know the answer now, but since I
was uncertain in the first place I still better ask, just in case.

(In reply to Gašper Ažman from comment #12)

> Converting to a pointer-type is not weird; it's converting to pointer
> to your own type that is. Any proxy object with a conversion operator
> to its underlying will have that if the underlying happens to be a
> pointer type. Such a proxy object might well forward methods directly
> to the underlying object via a conversion instead of stamping out a
> body for each cv-ref qualifier.
> 
> > Granted, I think this type of use should also have some sort of warning
> > available as it is still weird.
> 
> I disagree; conversion to your own type I think is a reasonable
> warning (unless you have a conversion operator in the same class)
> because it can come with a fixit hint (replace with reference to S
> instead). All other types are legit.
To be clear, when I say a warning should be available, I don't mean to imply
the thing it warns for isn't potentially legitimate. There's quite a few
warnings for legitimate things that you don't want to accidentally do but are
legitimate, or even common. The best example I have is everything under
-Wconversion, I know some people find suppressing it with explicit casts
annoying, but the errors that can come about through accidental conversions are
severe enough to justify using it in my opinion.

I would extend that rationale to not suppressing warnings for S* when a
conversion operator is present. Just because the conversion operator exists
that makes a call to that function (in the absence of any other overloads) well
formed does not mean that one meant to declare the xobj as S*.

> > I think a warning for declaring an explicit object parameter as a pointer to
> > base, derived, or deduced type should be in, since a reference is probably 
> > the
> > best choice in that case.
> 
> I definitely agree that this warning is reasonable.
> 
> > Perhaps deduced type shouldn't be included in that
> > warning, as there are cases that are kind of compelling for having that, one
> > could trap implicit conversions to any pointer type that way on derived
> > classes.
> 
> Yeah, deduced is legit, no warning for that one unless we discover
> active pathologies.
Noted, warn for pointer to base or derived, but not to pointer to deduced. Or
at least not behind the same warning flag.

> > To elaborate on what I said above, I still think a warning when using a type
> > that is not related to the class should be provided.
> 
> Strong disagree, that'll break a lot of code I actually intend to
> write. Not stamping out additional template instantiations /  better
> ABI by having implicit conversions will, I expect, be quite the common
> implementation trick (for instance, any member algorithm on a vector
> that doesn't change size can be called on an equivalent span instead
> and therefore be passed in registers). This can be an implementation
> detail of vector, so that vector and array can share template
> instantiations, for instance.
I think you should recognize that these exotic use cases are just that, exotic.
I don't think I should leave warnings that might be useful for the average user
off the table just because you or I would never enable that warning. Writing
this I'm now realizing the case I mentioned earlier that you said is not weird
would be better referred to as "exotic", I don't want to imply any negative
connotations.

I REALLY like the new exotic things we can do with this feature.

In short, I agree that this warning would be detrimental for implementers and
advanced users, but I don't think that's a reason to not have it optionally
available. (Granted, I have changed my mind a bit as I note below.)

> > Even given that my example
> > above presents a legitimate pattern, I think it's use is infrequent enough 
> > to
> > provide an optional warning for this. I question if there's any value in
> > suppressing the warning when there are legitimate conversions present, as it
> > would no longer inform the user that they might have done something by 
> > mistake.
> 
> I think my ruminations above give a good example of why such false
> positives should definitely be expected in well-behaved code.
Yeah, false positives would be impossible to avoid, any warning that tries to
take into account available conversions would need to document that false
positives are known to occur, and it won't be fixed. If one doesn't like that,
they shouldn't use the warning.

> > Essentially, since it's not possible to warn that there are no viable
> > conversions without false positives occurring, I think the warning should be
> > present simply because you are doing something weird, not because you are
> > declaring a function that won't be selected by overload resolution.
> 
> Yes, but you can get a pointer to it and call it directly. Might be a
> useful way to pass overload sets.
Could you elaborate on this, it sounds interesting but I think I'm not
understanding fully.

> > Just to reiterate, I fully agree that both warnings should be behind a 
> > flag. I
> > think I would propose that the first (warning for pointer to base, derived, 
> > or
> > deduced type in an explicit object parameter) should be turned on by 
> > default,
> > while the second should be turned off by default.
> 
> I'm not sure the second one should even exist, as it's bound to be
> even more annoying than -Wshadow.
Yeah, I run a LOT of warnings and I had to turn off -Wshadow. Too many false
positives, negligible benefit, and it can even make your code worse at times if
you try to follow it. I certainly wouldn't enable the second warning I referred
to here, I just don't think whether I would use a warning or not is a measure
on it's value.

Or maybe my philosophy on warnings is just flawed out of the gate? Perhaps I
should just forget about warning against any exotic use cases until there's
precedent that users want it.
I can imagine someone might accidentally put an unrelated type as an explicit
object parameter when they meant to put the type, but thats just it, I'm
imagining it. So I guess I'll wait for more precedent to emerge before I think
about it more.
I guess that's pretty much what you meant when you said this above.
> Yeah, deduced is legit, no warning for that one unless we discover
> active pathologies.

As a side note, through writing this I realized that I'm not actually sure if
there's a legitimate reason to take a reference to a base type as an explicit
object parameter, maybe just as a shortcut I guess instead of having to upcast
on each use. (With that said, taking a base by value to intentionally slice the
object would be legit)

> >
> > struct S : B {
> >     int f0(this B*) {} // warning 1, on by default
> >     int f1(this S*) {} // warning 1, on by default
> >     int f2(this auto*) {} // warning 1, on by default?, separate warning?
> >
> >     int f3(this int) {} // warning 2, off by default(?)
> > };
> 
> I say no warning on f2. S could be a CRTP-sans-CRTP mixin that expect
> that conversion to exist.
> 
> >
> > On the other hand, warning 2 could be split into 2 warnings, where one would
> > warn for a "weird" explicit object parameter, while the other would warn for
> > uncallable functions due to a lack of implicit conversions.
> > warning 2: weird explicit object parameter
> > warning 3: uncallable function (possibly has false positives)
> 
> I expect f3 to be a very common thing.

Given my thoughts above, I now agree that only f0 and f1 in the example should
be have a warning emitted. If precedent emerges to add more warnings it can be
done later, I was definitely getting ahead of myself.

> >
> > > Also, don't forget explicit object function bodies and their type behave a
> > > lot more like static functions than member functions.
> >
> > Yes, that is one of the next thing's on my list to implement. At the moment
> > they are still member function pointer types.
> > My current plan is;
> > 1. Pass reference to object when calling explicit object member functions.
> > 2. Convert the function type of explicit object member functions to regular
> > function types.
> > 3. Make sure calling explicit object member functions as member functions 
> > still
> > works.
> > 4. finally, implement calling explicit object member functions through a
> > pointer to function. (Which should hopefully be free to implement.)
> 
> What about forbidding the use of `this` and no implicit lookup in
> bodies? You basically have to implement body parsing like it's a
> static. You've probably thought of this, just making sure you don't
> throw away a bunch of effort on the odd chance you missed it.

Yeah, but previously I thought it would be easier to continue treating it as a
member function. At this point I don't think that is actually the case (hence
my question at the top.)

> > A little off topic, but my function pointer wrapper example above got me
> > thinking about things and I don't have a better place to share them.
> 
> Like programmable surrogates? Perhaps https://wg21.link/p2826 might be
> of interest.
> 
I'll be sure to check it out when I have a chance, I'm going to take a break
now though, I need a reset since I was probably attacking this wrong from the
start. I'm pretty sure my current problem (rvalue and lvalue overloads are
considered equivalent) will be fixed by switching to treating the explicit
object member functions as if they are static functions.

Originally I had thought that it would be harder to modify the code that forms
calls to member functions, but it completely slipped my mind that you can also
call static member functions the same way as member functions, so that isn't as
big an issue as I thought. Ah well, I got to have fun reading overload
resolution code, that will probably be helpful when implementing other things
(I hope).

It was definitely a mistake the start at member functions and disable
irrelevant/incorrect member function functionality though. Despite this sharing
more with implicit object member functions in terms of the standard, on the
implementation side xobj functions definitely shares more with static member
functions. Hindsight is 20/20 as they say.

Reply via email to