nodiscard
philosophies
nodiscard
philosophiesGCC’s warn_unused_result
attribute has gotten a bad reputation by being, essentially,
broken for a decade or two — and it is still broken, as of this writing.
There is no way to suppress the resulting diagnostic.
This led to several bug reports against the GCC compiler:
-
Bug 66425 “
(void)
cast doesn’t suppress__attribute__((warn_unused_result))
” (June 2015, as yet UNCONFIRMED) -
Bug 25509 “can’t disable
__attribute__((warn_unused_result))
” (December 2005, resolved FIXED)
And it led to library writers avoiding __attribute__((warn_unused_result))
— even the writers of glibc itself!
- Bug 11959 “fwrite must not be declared with the warn_unused_result attribute” (August 2010, resolved FIXED)
Plenty of open-source projects use ugly workarounds for the GCC issue, such as rewriting
write(mainpipe[1], "", 1);
into
if (write(mainpipe[1], "", 1)) {}
(an example taken directly from DNS-OARC’s dnsperf
,
but for the moral equivalent, see Mozilla).
Another approach is to introduce your own non-annotated helper function
(such as write_wrapped
in hellerve/e).
It strikes me that there are two different rationales for “why to mark things nodiscard.”
The “GCC rationale”: Ignoring this return value will lead to security vulnerabilities! You must always check the result of this function or else your program has a security hole. We don’t care what your intent was; if you meant to discard this result, then your intent was wrong. You must never ever fail to check the result of this function. If we catch you trying to ignore the result, we will refuse to compile your program. We would rather have no executable than contribute to the production of an insecure executable!
The “MSVC rationale”: Ignoring this return value is probably a mistake. You didn’t really mean to discard
this result, did you? Your code probably doesn’t match your intent, and you should take another look at it
for typos. If you really meant to discard this result, then okay, but please cast it to void
so we know
not to bother you again.
Personally, I think the “MSVC rationale” is much closer to a helpful reality than the “GCC rationale.” We should help the programmer write the code that best matches their intent, not complain that the programmer’s intent was wrong.
The C++17 standard attribute [[nodiscard]]
explicitly follows the MSVC rationale: casting to void
is the normatively blessed way to suppress the
resulting diagnostic.
GCC correctly respects (void)
when the attribute is spelled [[nodiscard]]
;
its bad behavior applies only to the non-standard __attribute__((warn_unused_result))
.
Full disclosure: Intel ICC never respects (void)
, not even for the standard attribute.
__attribute__((warn_unused_result))
int wur();
[[nodiscard]]
int nd();
int should_warn() {
wur(); // OK, warns
nd(); // OK, warns
return 0;
}
int void_cast_should_not_warn() {
(void)wur(); // OOPS, warns on GCC (but not on Clang)
(void)nd(); // OK, doesn't warn
return 0;
}