-
Notifications
You must be signed in to change notification settings - Fork 740
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
2fc94db made gsl::not_null constructor explicit, why? #699
Comments
I have tried the following program with/without explicit constructor of
|
I am also surprised by this change - the whole point of not_null to me was an easy drop-in contract check encoded in function signatures that informed the caller that:
Thanks to this you didn't have to put boilerplate if'checks or asserts in the function body just to check parameter validity. But from the caller perspective this was transparent so it could be also used to improve already existing code, now suddenly everything breaks because it requires explicit casts that have no real benefit to them. So unfortunately, for me, this was an instant revert. |
I can understand that this change will cause existing users some irritation as they must update callsites. However, the point of this change - as per the guideline that recommends it - is to make it more explicit at callsites that you are passing a pointer to a function that will enforce it to be not-null. @kivadiu: this is not about changing runtime behavior, it is about making the actions that the compiler must enforce clearer to readers of the code. "Converting" constructors (such as the non-explicit form) are difficult for maintainers to reason about when reading through code as they are hidden. They can also participate in conversion sequences in ways that surprise users. Making these constructor calls explicit, in my opinion, also lines up with the "pay only for what you use" ethos the Guidelines encourage. This way callers to functions that take |
I am still not convinced. The guidelines about that topic are the following:
All examples are about function arguments, never about function call point. Even if I am in favour of coming back to the non explicit constructor for
|
@kivadiu You also need to consider the guidelines that strongly suggest avoiding implicit, single-argument constructors: C.164: Avoid conversion operators. But this is not a design created purely by looking at guidelines in some abstract vacuum. The types in the GSL were developed based on the experience of working with large, real-world codebases. Codebases that are forced to use an explicit constructor are also forced to rethink just how many unnecessary, previously implicit constructions they had (or would otherwise have), and as a result, they can tighten the contracts on their function interfaces. The work seems mildly annoying at first, sure, but results in code that is better-specified and easier to maintain. The function you provided seems a simple and helpful thing. I have a suspicion (that I haven't tested, so feel free to shoot me down) that template deduction guides in C++17 and beyond will allow you to skip the type while using the explicit constructor anyway, so a call could look like: |
Yes I have seen that somewhere so I think you are be right but I am still in C++14... I have also seen C.164 and I apply it in my code but I still do not know if this should really apply to C.164 says:
I wonder if it is not fundamental to have the implicit conversion here and if we do not touch here the "frequently needed" case. But having the explicit means that not_null will never be applied "automatically", without thinking first. So you may be right. |
@kivadiu My 2 cents, as I am currently experiencing the change myself:
|
I'll just add some more feedback. I'm strongly against this change.
I did start using GSL and specifically not_null a lot in my project Telegram Desktop and it helped me a lot. But now if I want to update the GSL to the latest version I need to fix about 2.5K errors throughout the project. So for me the only way to stay onboard right now is to stick with v1.0.0 version and after that perhaps to fork out my own not_null that I'll use instead of gsl::not_null. That's not a big problem, but I don't think it is good. I think implicit conversion |
What if we add GSL_NULL_POINTER_RELAX define to switch between strong gsl::not_null and old behaviour ? |
@vitlav: I'm not sure what that suggestion would achieve. It seems to fork the library at the source code level, which is rather undesirable. I'll point out to those who keep identifying the "boilerplate" code generated by this change, that having that code enables people to see where they are injecting restrictions on pointer values in their code and reason about those costs. With an implicit conversion at those sites, the conversions (and resulting checks) are invisible. C++20 contracts will offer a facility where you can inject requirements like "not-nullness" into a function's contract without requiring source code changes at callsites. They will also let you choose how much enforcement you'd like. That is a more general and flexible facility than anything like |
@vitlav @john-preston @kivadiu @twadrianlis @ericLemanissier and everyone who uses @neilmacintosh had good suggestions on how to address the adoption of explicit
|
@annagrin If we are able to write an automatical tool to change the code, maybe the need for sloppy_not_null vanishes? |
@kivadiu
So I don't see a reason no to do both (sorry for a double negative:) |
I am being worn down by this problem for large code bases that's been using So I am considering reversing the approach - make the Thoughts? |
@annagrin That seems like a reasonable and pragmatic approach to me. It at least provides a path for people to get to a more efficient and correct place ( |
I don't really get that point. Don't existing code bases with not_null just need global "replace in files" all occurences of "not_null" by "sloppy_not_null"? That does not look very hard to me. |
It's more involving that that, the following does not work anymore:
You need to change |
@beinhaerter my thinking points:
In short, since we are providing a sloppy and a strict type either way, we would like at least to minimize user's pain with version changes. And continue looking into contracts as a possibly better alternative. |
@kivadiu Your code compiles with GSL version 1 where @annagrin I appreciate that there is a new |
I found this very regrettable: opting for the "unsafe"/suboptimal choice by default isn't encouraging to use the best API moving forward. |
@joker-eph I agree that it's sub-optimal. But the argument I heard/read was that Microsoft already adopted the non-explicit In that case, I can see that taking this path allows them to keep up with GSL improvements and gradually adopt Yes, it would be nice if we got everything right with the "nice name" first time, but we didn't. We want to take as much code as possible to a better place. Even non-explicit |
@neilmacintosh I read this thread multiple times but I didn't find why " it is not as easy as just search-and-replace"? (Actually what I could find more compelling as an angle would be the argument that the type system in C++ does not help by not allowing to bind expressions like |
@joker-eph I suggest you re-read the first couple of bullet points of @annagrin's comment as they explain why global search-replace didn't work. |
@neilmacintosh note that there are 4 comments from @annagrin in this issue that contain bullet lists. I read all off them and still can't figure out, sorry.
Unrelated?
Unrelated?
This is related, but it explains that replacing works, not the opposite.
This is related, but it explains how to mass update to the strict version, not how to use the sloppy-one. I.e. it helps to move from sloppy to non-sloppy.
Argument in favor of sloppy.
Argument in favor of sloppy.
This is not a bullet point, but the first mention of a problem with the update. However it does not elaborate why a mass replacement wouldn't work.
This is saying it is problematic, but does not say a global search/replace wouldn't work (just that it would be annoying with merge conflicts?).
This does not address an issue with global search/replace as far as I can tell. |
@joker-eph In my opinion the impossibility of implicit constructor of |
@john-preston I think this is a valid argument, and as I mentioned previously it is a tradeoff that merits discussion. However it does not seem to be the motivation for the current code change. |
@joker-eph Apologies for missing your questions, will answer them here in bulk:
The safety of
Places where constructor calls need to be inserted are not simply searchable, you would need symbols and types to detect those, for example:
Absolutely. The problems are that there are none yet and not all code compiles with clang. Changing abruptly and requiring modifications in all code without any tooling available is painful, opting into
The problem with creation of |
Thank for elaborating @annagrin ! |
I just thought of another solution to this explicit/non explicit constructor: a macro could be used to say if the constructor of
|
That might be more a Visual Studio problem than a GSL issue, so I just link it here for the records. My latest tests with VS2019 show that the compiler emits a lot of lifetime.1 and lifetime.3 warnings with |
Since 2fc94db, this program does not work anymore without explicit cast to not_null (here tested with clang 5.0.2 on linux with -std=c++14):
If this is really wanted, could you explain why?
Kind regards
The text was updated successfully, but these errors were encountered: