See https://wg21.link/p0847r7
.
Related: https://cplusplus.github.io/CWG/issues/2653.html
I have some elements working so far, I opted to implement parsing for `this` in cp_parser_parameter_declaration instead of in cp_parser_decl_specifier_seq because I didn't want to add another member to cp_decl_specifier_seq, but I ended up doing it anyway in the end. The `this` clause isn't a specifier so I thought including it in the same function for parsing decl-specifier-seq would be odd, but I ended up needing to put code to skip the first `this` token into cp_parser_decl_specifier_seq so I don't know if that was the right decision in the end. Ultimately I would like to refactor attribute parsing into another function instead of having it be a part of cp_parser_decl_specifier_seq, but I'm not going to do that while implementing "deducing this." I'm uncertain of where to put the error checking, right now it is in grokdeclarator. Setting the "explicit object function flag" is also in grokdeclarator at the moment as I wanted to avoid adding more parameters to functions. I'm pretty unhappy with how the code is organized, so if anyone has any opinions on where certain things should be done, please share them. Right now, parsing the `this` clause works, and errors are reported when it is used other than in the first parameter, with an altered diagnostic when it is used multiple times. There is also a diagnostic when `this` is used in a function type declaration. Input would be appreciated, thanks. (In reply to Marek Polacek from comment #2) > Related: https://cplusplus.github.io/CWG/issues/2653.html I don't currently implement this, but I was going to, however I am unsure on whether it's been merged into the standard, I guess "Status: C++23" means that it was, yeah?
As one of the authors, I can assure you you never need to implement this for c++23.
And of course by "this" I meant support for a default argument on the explicit object parameter. We might add it back in the future if we find a usecase.
I've noticed the standard does call `this` a specifier, I will perhaps rework the code to just do parsing in cp_decl_specifier_seq. (In reply to Gašper Ažman from comment #5) > And of course by "this" I meant support for a default argument on the > explicit object parameter. > > We might add it back in the future if we find a usecase. Great, I will just forbid it then, I can't think of a compelling reason to make it a warning even if it should be benign.
struct S { int f(this S*) { return 5; } }; int main() { S s{}; return s.f(); } Here is my current progress, this code works. I have a good feeling that the rest is going to be easy. Except for deduction maybe, but I have a minor hunch that it might -just work- without any extra tinkering after everything else has been implemented. Also yes, I know that `int f(this S*)` is not a valid declaration, I just didn't have to change any member function call code for it to work this way so I made it my first goal to implement it this way. Actually, I guess it's not explicitly invalid is it? I don't recall seeing it in the pathological cases section of the paper, indeed, I just checked and there doesn't seem to be mention of it. I believe it should probably be valid though, not useful if you ask me. Even less so than the other pathological cases (which I think might actually prove to be useful in some cases.) However, regardless of my opinion, following the same direction as the original paper, I imagine it should be allowed. The class would just need an implicit conversion to pointer, maybe a warning would be in order though?
I don't see anything forbidding that declaration, but I think it can only be called if S& has an implicit conversion to S* because the object parameter is an lvalue of type S, and so it can only match S* via an implicit conversion.
If we're right about that, then I agree that a warning would be useful for classes that have no such implicit conversion from S to S*. I think the warning would give a false positive in the case below, so a way to disable it locally would be needed (e.g. via a diagnostic pragma). struct S { int f(this S*); // warning: explicit object parameter of pointer type can only match if an implicit conversion to S* exists }; struct T : S { operator T*(); }; T t; int i = t.f(); // derived class has the required conversion
Yes, the explicit object parameter always receives the cv-l/r qualified reference to the object of the call. Implicit conversions are then of course allowed, same as any other parameter. S* is not that useful as a type of an explicit object parameter, implicit conversions to pointer to yourself are probably about as weird as overloading operator&(). Also, don't forget explicit object function bodies and their type behave a lot more like static functions than member functions. On Sat, Aug 19, 2023, 10:43 redi at gcc dot gnu.org < gcc-bugzilla@gcc.gnu.org> wrote: > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102609 > > --- Comment #9 from Jonathan Wakely <redi at gcc dot gnu.org> --- > If we're right about that, then I agree that a warning would be useful for > classes that have no such implicit conversion from S to S*. > > I think the warning would give a false positive in the case below, so a > way to > disable it locally would be needed (e.g. via a diagnostic pragma). > > struct S { > int f(this S*); // warning: explicit object parameter of pointer type can > only match if an implicit conversion to S* exists > }; > > struct T : S { > operator T*(); > }; > > T t; > int i = t.f(); // derived class has the required conversion > > -- > You are receiving this mail because: > You are on the CC list for the bug.
(In reply to Jonathan Wakely from comment #9) > If we're right about that, then I agree that a warning would be useful for > classes that have no such implicit conversion from S to S*. > > I think the warning would give a false positive in the case below, so a way > to disable it locally would be needed (e.g. via a diagnostic pragma). > > struct S { > int f(this S*); // warning: explicit object parameter of pointer type can > only match if an implicit conversion to S* exists > }; > > struct T : S { > operator T*(); > }; > > T t; > int i = t.f(); // derived class has the required conversion I've ran some ideas in my head, no matter what is the decided upon design I fully agree that all the warnings must be implemented with flags so they can be suppressed, each independent of the other. I explain the warning I would propose below, and I will make sure each one is properly documented, especially since one of them has a clear risk of false positives, as you demonstrated. (In reply to Gašper Ažman from comment #10) > S* is not that useful as a > type of an explicit object parameter, implicit conversions to pointer to > yourself are probably about as weird as overloading operator&(). The only compelling case I can think of for such a thing would be passing a pointer type that isn't related by inheritance anyway. That is to say, I'm not disagreeing with you, I just stopped to think about it and came up with this, consider the following example. struct my_function { using func_ptr = void(*)(int); func_ptr _f; void operator()(this func_ptr f, int a) { return f(a); } operator func_ptr() const { return _f; } }; I'm mostly just excited to share what I finally thought of as a good example for an explicit object parameter that is not of a deduced or base type, but it's never the less a pointer type. Granted, I think this type of use should also have some sort of warning available as it is still weird. 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. 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. 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. 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. 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. 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. 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(?) }; 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) struct S { operator int() const {} int f0(this int) {} // emits warning 2 int f1(this some_type) {} // emits warning 2 or 3 }; I imagine my proposed warnings 2 and 3 should be mutually exclusive, but I'm not sure which should take precedence. I suppose I've bikeshedded this a bit but I wanted to get my ideas up here before I start working on things again today. > 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.) 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. The example only really feels good because the implicit conversion is already fairly natural for the type. What about cases where an implicit conversion doesn't make sense, but still wants to benefit from such a pattern? I have to wonder if a solution would be allowing private conversion operators to be selected when calling an explicit object member function. I guess that doesn't work properly when calling it through a pointer to the function though, so surely this would have more issues than it's worth. I have some other bad ideas on how to facilitate this, but they are far too cursed to share here, so I will stop stalling and get to work.
Replies to relevant snippets inline. That was quite a write-up! > The only compelling case I can think of for such a thing would be passing a > pointer type that isn't related by inheritance anyway. That is to say, I'm not > disagreeing with you, I just stopped to think about it and came up with this, > consider the following example. > > struct my_function { > using func_ptr = void(*)(int); > func_ptr _f; > > void operator()(this func_ptr f, int a) { > return f(a); > } > > operator func_ptr() const { > return _f; > } > }; This case is already covered by surrogate functions: struct S { using fptr = int(*)(int); operator fptr() const { return +[](int){return 42;}; } }; int main() { S x; x(3); // OK, returns 42 } https://godbolt.org/z/48Efzdr1q The conversion operator is a better match in this case, so the operator() which requires an implicit conversion is not the one called. > > I'm mostly just excited to share what I finally thought of as a good example > for an explicit object parameter that is not of a deduced or base type, but > it's never the less a pointer type. 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. > 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. > 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. > 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. > 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. > 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. > > 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. > > > 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. > 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. > The example only really feels good because the implicit conversion is already > fairly natural for the type. What about cases where an implicit conversion > doesn't make sense, but still wants to benefit from such a pattern? I have to > wonder if a solution would be allowing private conversion operators to be > selected when calling an explicit object member function. Intriguing thought. > I guess that doesn't work properly when calling it through a pointer to the > function though, so surely this would have more issues than it's worth. > I have some other bad ideas on how to facilitate this, but they are far too > cursed to share here, so I will stop stalling and get to work. :magic wand emoji:
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.
> 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. That's how I implemented this in EDG - static member functions that just have some extra powers. > Of these cases, is f0 or f1 and g0 or g1 correct? I assume the answer is f1 and g1. Both are correct - you're still a member of S, so you don't have to qualify S::my_type (I mean, you can, it's not incorrect, but it's just not necessary - means the same thing either way). > When deduced, f0 or f1 and g0 or g1? I would definitely think f1 and g1 now. These now might actually mean different things. Unqualified my_type is still valid and means S::my_type (i.e. int). But Self::my_type could now mean a different type. Merging the two examples: struct S { using my_type = int; template<typename Self> void f0(this Self, my_type); template<typename Self> void f1(this Self, Self::my_type); }; struct D : S { using my_type = double; }; D().f0(2.0); // calls S::f0<D>, which takes an int D().f1(2.0); // calls S::f1<D>, which takes a double
Created attachment 55793 [details] inital support for P0847 explicit-object-parameter Alright, I finalized something that I hope is worthy of criticism. I haven't ran tests on it yet but I think it should be relatively stable. My first time around I made the mistake of having hard failing TREE_CHECKs in if conditions, and I'm pretty sure that was causing problems with the tests (I saw a lot of segfaults), but I'm fairly sure it should be good this time (not that I shared the first one). I will probably start tests first thing tomorrow, hopefully I can figure out how to make it take less than 4-8 hours. I'm pretty happy with where I put most things, I didn't add anything to the core tree nodes, I instead used tree_decl_common::decl_flag_3 for PARM_DECL, but I added a member to lang_decl_base, hopefully this is satisfactory. As for what I know works right now, the below program outputs 15, 25, 35, 45 as you would expect. I haven't tried lambda's but I am sure they don't work. I have not tried anything with inheritance, I wouldn't bet on it working but I wouldn't bet against it. I have not tried implicit conversions, but I have a feeling they probably work. I was planning on implementing rejection of qualifiers on xobj member functions but I forgot, so that will come tomorrow. I also have to implement errors when trying to declare a xobj parameter in a function type. I haven't tried taking the address of an xobj member function, but I have a hunch it will work, they are almost entirely treated as static member functions at the moment. Speaking of that, the function declaration gets pretty printed as a static function at the moment too. So as you can see, there's still lots to do, but it shouldn't be as hard now that I am more familiar with the code base. Something I'm not especially happy with is how the error checking is strewn around. I tried to put it where it is most relevant, but prioritized putting it where I could get the best diagnostics, but I'm probably missing some things I could be doing to better group it together. I am also not happy with the quality of all of the diagnostics, I want to improve on that as I learn the quirks of the utilities. Please criticize, I am certain I am still doing some stuff wrong, so I would appreciate any input so I can correct those mistakes. Here is the aforementioned program that I know to work on my system. Surely nothing can go wrong, but who knows. #include <cstdio> struct S { int _a; int my_func(this S& s) { return s._a + 5; } int my_func(this S const& s) { return s._a + 15; } int my_func(this S&& s) { return s._a + 25; } int my_func(this S const&& s) { return s._a + 35; } template<typename Self> int my_func_dispatch(this Self&& self) { return static_cast<Self&&>(self).my_func(); } }; int main() { S s{10}; printf("%d\n", s.my_func_dispatch()); printf("%d\n", static_cast<S const&>(s).my_func_dispatch()); printf("%d\n", static_cast<S&&>(s).my_func_dispatch()); printf("%d\n", static_cast<S const&&>(s).my_func_dispatch()); }
Just gotta note that the patch posted here had an oversight, it never worked as I hoped. The good news is, I submitted a finalized patch to the patch maillist, just before I have to leave. Thanks for the help everyone, I will check in but it will probably be a while before I can work on anything in a dedicated fashion for a while. I will certainly respond to any chatter on the thread of my patch if anything comes into question though. I have to go catch a plane now, thanks again.
Read through the patch quickly, want to suggest a few tests. When a lambda has captures, the explicit object parameter is used to get at them *silently*, if they are related, otherwise the program is ill-formed: int x = 42; auto f1 = [x](this auto&&) { return x; }; auto f2 = [](this auto&&) { return 42; } static_cast<int(*)(decltype(f1)&)>(decltype(f1)::operator()); // OK static_cast<int(*)(int&)>(decltype(f1)::operator()); // ERROR static_cast<int(*)(int&)>(decltype(f2)::operator()); // OK
(In reply to Gašper Ažman from comment #17) > Read through the patch quickly, want to suggest a few tests. > > When a lambda has captures, the explicit object parameter is used to get at > them *silently*, if they are related, otherwise the program is ill-formed: > > int x = 42; > auto f1 = [x](this auto&&) { > return x; > }; > auto f2 = [](this auto&&) { return 42; } > > static_cast<int(*)(decltype(f1)&)>(decltype(f1)::operator()); // OK > static_cast<int(*)(int&)>(decltype(f1)::operator()); // ERROR > static_cast<int(*)(int&)>(decltype(f2)::operator()); // OK Ah I see, thanks for the input. I was actually wondering about that, especially if you were expected to use the explicit object parameter to access captures. Is my understanding correct that the second case would not be an error if captures were not used in the body of the lambda? Based on your wording I would believe that is the case, so implementing this behavior would not be as simple as just disallowing unrelated deduced types when there are captures. Unfortunately, I will not have access to my computer for a while more, I plan on setting up an environment at another computer and fixing the formatting errors that were noted, but I will wait until I have access to my computer before I work on it more seriously I think. Thanks a lot for looking at the patch though, I really appreciate the time you've taken.
Correct, the use of the capture is the source of the error, not the instantiation with an unrelated type. On Sat, Sep 2, 2023, 09:54 waffl3x at protonmail dot com < gcc-bugzilla@gcc.gnu.org> wrote: > https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102609 > > --- Comment #18 from waffl3x <waffl3x at protonmail dot com> --- > (In reply to Gašper Ažman from comment #17) > > Read through the patch quickly, want to suggest a few tests. > > > > When a lambda has captures, the explicit object parameter is used to get > at > > them *silently*, if they are related, otherwise the program is > ill-formed: > > > > int x = 42; > > auto f1 = [x](this auto&&) { > > return x; > > }; > > auto f2 = [](this auto&&) { return 42; } > > > > static_cast<int(*)(decltype(f1)&)>(decltype(f1)::operator()); // OK > > static_cast<int(*)(int&)>(decltype(f1)::operator()); // ERROR > > static_cast<int(*)(int&)>(decltype(f2)::operator()); // OK > > Ah I see, thanks for the input. I was actually wondering about that, > especially > if you were expected to use the explicit object parameter to access > captures. > Is my understanding correct that the second case would not be an error if > captures were not used in the body of the lambda? Based on your wording I > would > believe that is the case, so implementing this behavior would not be as > simple > as just disallowing unrelated deduced types when there are captures. > > Unfortunately, I will not have access to my computer for a while more, I > plan > on setting up an environment at another computer and fixing the formatting > errors that were noted, but I will wait until I have access to my computer > before I work on it more seriously I think. > > Thanks a lot for looking at the patch though, I really appreciate the time > you've taken. > > -- > You are receiving this mail because: > You are on the CC list for the bug.