-
Notifications
You must be signed in to change notification settings - Fork 5.4k
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
[[nodiscard]]
advice is largely absent
#2109
Comments
The problem is that even though it is "more correct" to make such functions The C++ standard only uses
I feel like the guideline should use this much weaker requirement:
|
That's not because the standard thinks it shouldn't be used elsewhere. There have been several proposals to add it to loads of functions in the standard library, but I (and others) argued strongly against that, because:
And the Right Thing is to use it widely, everywhere that it makes sense. IMHO compilers should warn about discarded values for all comparison operators, all tl;dr do not assume from the absence of * I wish compilers were smart enough to warn about it without being told to, e.g. equality comparisons for |
The question is whether this makes sense as a guideline though. Afaik, the guidelines also don't recommend putting top-level Do we really want the core guidelines to recommend code which looks like this: template <integral Int>
[[nodiscard]] // let's use nodiscard; after all, it's more correct
// don't forget the constexpr; after all, it can be constexpr
// let's make all the parameters const too; after all, it's more correct
constexpr Int max(const Int paramA, const Int paramB, const Int paramC)
// while we're at it, we also need a conditional noexcept specification
// this code is just oozing with correctness now!
noexcept(noexcept(max(paramA, max(paramB, paramC))))
{
return max(paramA, max(paramB, paramC));
} I know this is a bit of slippery slope fallacy, but you get the point: we pay a price in readability when we add all of these features that make our code "more correct". The responsibility should really be on compilers to detect when a function contains no side effects, and mark those functions as implicitly |
Yes, I agree. What's important is to get warnings about things that are probably typos or logic errors. Decorating functions with |
I agree with @Eisenwave , fwiw. To respond to @jwakely 's example, yes, of course this can catch real errors. But:
The At the very least hopefully we can agree that putting nodiscard on a huge percentage of function is at least not a consensus best practice or close to it; I think the core guidelines should be conservative here to start and only recommend it in a narrower set of circumstances where it is widely agreed. And in particular, the best usage of nodiscard in my view is actually on types. You can put nodiscard on a type that carries an error, and this gives you significant real value, and avoids verbosity (it's a real pity std::expected isn't, or seemingly cannot, be mandated nodiscard). Beyond that, I'd mostly put nodiscard on functions that return error types, or values that need to be checked/compared against (e.g. C style functions returning integers for number of bytes written), and functions that are misleadingly named (like Edit: looking back at this; I think I'd support the guideline also suggesting it for == and any arithmetic operator with a compound version, for the reasons discussed. But I still wouldn't suggest going extremely broad with it. |
I see you edited your comment to remove:
Do you know why GCC warns about it? Because I added And I had to do that because otherwise GCC doesn't warn for user-defined comparison operators (Clang does though). Which is precisely the point I've made above. Not all compilers are smart enough to do it without the attribute. I'm not arguing that the guidelines should say to use nodiscard, I was originally just commenting on the claim that "The C++ standard only uses [[nodiscard]] when it resolves some ambiguity". The few places that use it were added before the current policy of not bothering to specify it was decided on. The Core Guidelines are for writing C++, not writing C++ specifications, so the standard is not a good example to follow here. Real C++ stdlib implementations do use nodiscard widely. |
That depends. A library with a million users might save 5 million minutes per year, which seems more useful. |
Yes, I went back and double checked the reason why it did that, and saw that was in the source. I was just surprised because tbh this seemed like a good warning to have and one that's reasonable to implement. This is a fair point of course.
I agree that the specification isn't a good example. I also think the stdlib is barely a better example; 99% of C++ developers aren't writing code that will have millions of users, and whose source code is read so rarely because you have absolutely pristine documentation on cppreference.
FWIW when I'm thinking of the verbosity costs, I'm largely thinking about reading rather than writing, e.g. like in Eisenwave's example of what happens when you "max out" correctness. And that costs scales with readers of course too, so the number of users doesn't really affect the calculus very much. I think the comparison between nodiscrard, and const-ing values in the definition is pretty apt. In both cases it's strictly more correct when applicable, yes, but it's also a very small benefit, so small that I think there's definitely room to say that it doesn't outweigh the verbosity cost (which is low as well, of course). Perhaps we could try to break down a few specific "cases" where nodiscard could be applied, label them (1-N or A, B, etc) so that we're all talking about the same thing, and see how folks feel about each one. |
Not really. Const parameters are only relevant/helpful for the function implementation. [[nodiscard]] Is relevant for every single call site of that function. Also, just saying: #of readers != #of users, so I'd challenge the claim that the # of users doesn't make a lot of difference. Personally I started to put nodiscard on every sideeffect-free function that belongs to the API surface of a library and sometimes even on internal functions. That said "I like/dislike", "it helps/doesn't help me" are imho usually very poor arguments for why something should or should not be put into a core guideline. Just because something is useful/not useful for me/my team doesn't mean the same is true for the majority of projects out there. |
Yes, you make good points. Without getting further into the details though, I agree with your last comment, which I think is aligned with what I said earlier about it not being a clear cut, consensus best practice. I'd be against nodiscard on every single side-effect free function in the core guidelines; so far nobody other than OP has said they're actually in favor. To make things a bit more concrete; categories of functions that we could consider suggesting nodiscard on:
I would say I'm presently in favor of 1-2, perhaps 3 and 4 also. For 4, in most cases just renaming your function is better, but if you have a poorly named function and can't tolerate an API break perhaps that's your only option. |
|
It's difficult to encapsulate all of these scenarios in a single sentence, and that's ultimately what we need for a guideline. I would approach this in reverse; i.e. starting at the wording and seeing what implications it has: Wording A
This policy is a trade-off which is similar to how it is decided whether a function in the standard library is Wording B
This policy is most aggressive in promoting Wording C
This policy is more in line with B, but constrained to cases like Wording D
It's exclusively aimed at misuses of functions like When is
|
Category | A | B | C | D |
---|---|---|---|---|
1. Returning errors | maybe | maybe | maybe | maybe |
2. Returning information about side effects (e.g. read ) |
maybe | yes | maybe | yes |
3. Typo hazards like == vs = |
no | yes | maybe | no |
4. Mutation ambiguities like empty() |
yes | yes | maybe | no |
5. All pure functions | no | yes | yes | no |
"Returning errors" is maybe
in all cases, because discarding errors is common and often intentional, like for std::printf
.
It's also possible that even if we discard an error, we might get error information from elsewhere, like errno
.
Overall, I don't think that the guideline should extend to 5.
and 3.
, but it's very difficult to come up with wording that doesn't. I still prefer my original wording A
.
I noticed today that
[[nodiscard]]
isn't present in this set of guidelines (apart from not ignoring results of functions declared nodiscard and not using void casts to ignore). I think there are some clear cases where guidance would help people:std::expected
and almost alwaysstd::optional
's.Some broad guidance may also be useful, such as:
[[nodiscard]]
"The text was updated successfully, but these errors were encountered: