-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Why should we allow leaking temporaries in C++23, since we can delete them with "deducing this&&"? #2276
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
Comments
Maybe I'm missing something, but your overload set doesn't allow calling
But what about this perfectly fine code?
Isn't that also prevented? |
Thanks for the interesting comments @jwakely, C++ is indeed quite creepy sometimes...
I believe that a safe language, or at least some safe guidelines, should not consider this as a safe and valid code... I think this only works because of lifetime extension, as a corner case, not some regular case (although I'm not fully certain of this).
It's interesting this && notation after function from C++11, I didn't know about it before!
If there's a way to do that on C++11 it would be much better than depending on c++23 features indeed. |
No, it is perfectly regular and it has nothing to do with lifetime extension. It's literally just normal lifetime rules, no extension involved whatsoever. The temporary returned by
No. There is nothing wrong with using temporaries this way, the language guarantees that it's safe. For another example:
Should this be prevented? The function creates a
The first one should be
|
Thanks for that, now this works on C++11! Regarding the examples, it's getting even more interesting with std::string_view, that's also kind of flawed too.
Note that the example you mentioned involves passing a std::string to std::string_view (not .c_str()), but even in this case, Yes, it must be prevented, if following the same reasoning of this post:
This badness is of a similar nature as the one mentioned in the guidelines:
This badness looks like this one:
Note that current linter, at least for clang, it cannot detect this error.
My conclusion is that std::string_view should also never build upon a dangling std::string&&, as this is also risky:
So, another conclusion could be that we should never create views upon dangling objects, only using them to std::move.
This correctly fails to compile:
In short, two sides of the same coin: Finally,
Surely, this is a trade between ergonomics and safety, so a warning is typically better than an error, unless the developer does not make mistakes of this kind. This is a common error. Maybe newer library developers can use these advices to enforce future safer code, not breaking existing std::string and std::string_view, which is likely impossible to change, but at least for simpler cases of views not risking the same problems that currently exist on std::string_view and related types. |
This text is very interesting, thanks for sharing! Almost the same discussion here... author describes a String class containing a std::string and an operator std::string_view, and a TokenIterator class containing a const char*. Then it realizes that disabling operator std::string_view && resolves some issues (the same as here). Then it disables String&& constructor at TokenIterator to prevent taking a view from a corpse (same as here). I only don't understand why he went so far, to just label it "The unfortunate “solution”", since it actually resolved the issues above, same as here. Even here in the guidelines this is enforced:
http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#Rstr-view
However, the example itself should be improved, IMHO, because it contradicts itself:
Why is "const string&" considered OK here, instead of std::string_view, since std::string_view is obviously cheap and value-based? Finally, since passing "const string&" and "const char*" are similar in cost and behave almost the same, std::string_view should replace both... at least string_view is advised to replace "const char*" on C.49: Prefer initialization to assignment in constructors. At this point, I just assume const std::string& is a bad approach compared to std::string_view, so if std::string_view was used, code would be correct. -- I don't mean, like the author did, to call it our “dangling-reference-proof” String class, because nothing in C++ can protect us from badness that happens outside our scope. The intention here is to show that it is inherently bad to leak internal references on this&& and to build upon rvalue constructors of other classes to make views. So, a simple change on the authors' code, according to these Cpp Core Guidelines, easily fixes the issue:
I just added a std::string_view on the constructor of TokenIterator:
Then test() runs correctly, instead of crashing:
Taking view data from a const std::string& is just as bad as taking view data from &&, since both could be dangling anyway. Just to be crystal clear that the author wrote a very nice text, just to do a silly bug in the end, to try to contradict all the good ideas presented before (and unrelated to the bug). What he wrote, instead of TokenIterator, consider a class SillyBug:
This code breaks because of flawed const string& (and wouldn't with std::string_view) and its unrelated to the && approach discussed before... in fact, this bug demonstrates (again!) how dangerous is to construct things based on dangling stuff like const string& or string&&. |
Because the "solution" actually causes other problems, and doesn't solve all lifetime bugs, and that's because value category is not lifetime. It is a mistake to think "lvalue good, rvalue bad". It's not that simple, and trying to solve lifetime issues by using C++ features that can only tell the difference between lvalues and rvalues is misguided. Because value category is not lifetime.
You seem to be ignoring the first problem, that Anyway, it's naive to say that
But also SL.str.1: Use std::string to own character sequences.
No, the TokenIterator would still have all the same issues with dangling.
What makes you think this is correct? Previously
For one specific, trivial example. But your change doesn't help at all for plenty of other uses:
Your changes don't help at all here, neither does preventing construction from a temporary
And so can a |
The author didn't even implement the complete TokenIterator class...
If the size is necessary here, it can be used as a marker as well.
There are differences of course to a string_view, but in the context of this proposal is clear that const string& and string&& have equivalent problems, that's what I'm pointing out here... it makes no sense to block string&& while allowing const string& to do exactly the same damage.
Note that the authors considers this "reasonable", as I don't, as pointed out above... I'll copy from the article:
As pointed out above there is something intrinsically wrong with this code :
It can dangle only if its input is already dangling, but it cannot do what SillyBug does... promoting a fine static/global
My proposed changes cannot solve this, you are correct, but it is not meant to solve all problems (since this case the string would not be a string&&). But it does solve the known problem also pointed out in the guidelines, which is a common problem:
That's the focus of the proposal. And I agree with you that some valid ergonomics is lost, specially this one: It's hard to change standard stuff like std::string and std::string_view, but maybe, at least this could help issuing Warnings for users that they should pay attention to these situations, as currently the linters are unable to. |
Sorry if this is not the right place to propose some ideas for cpp core guidelines, but I could not find any other place to begin a discussion. This proposal tries to create a new rule that prevents common dangling pointer errors with c++23 "deducing this".
In C++, it is typical to have dangling pointer errors in std::string code using c_str(), and it seems that tooling is able to warn them in some situations, like for std::string with clang Wdangling-gsl:
If user ignores the warning of Wdangling-gsl or does not use -fsanitize=address, then it may cause unexpected problems. It also seems to be hardcoded to detect only the case of c_str() from std::string, as it does not issue warnings on similar cases. Consider MyString:
Since c++23 we have "deducing this" (https://wg21.link/P0847R7), I believe we are finally able to delete these dangerous dangling operations, when we are certain they will fail:
This way, this flawed code fails to compile:
I could not find such discussions in the P0847R7 paper itself, and also could not find any guideline here that incentives people to delete this&& methods, specially when resources are leaked.
For example, the length() operation does not seem to be so evil as c_str(), since it only leaks a copy of the size:
So, there's no need to delete ALL functions that are rvalued on this, only the ones that leaks internal resources. I found this on cpp core guidelines:
If this is still an open issue, maybe C++ std::string should implement this C++23 strategy for c_str(), and other similar cases, like vector iterators, etc, and while this does not happen, could core guidelines incentive this strategy? Or is there some unknown or serious drawback on enforcing that?
Another similar case that can be solved with this strategy is related to for-range, that also dangles:
A recent solution is to do use c++20 range init instead:
However, I believe the real problem here is the items() function that it's inherently flawed, and should disallow leaking internal resources when rvalued:
This correctly generates an error, even with classic for-range:
Thanks for your time, and I hope this advice can help others, or even improving the c++ STL someday.
The text was updated successfully, but these errors were encountered: