Description
We love [[nodiscard]]
in the STL. We love it so much that we've marked over 3,000 functions, and gotten special compiler support to enable it in C++14 mode (even though it's a C++17 attribute). This finds real bugs that are hard to find otherwise. (We're using a _NODISCARD
macro in order to provide an escape hatch and support compiler front-ends without [[nodiscard]]
, but we'll probably remove that macro someday and just use the attribute directly.)
However, while our usage of [[nodiscard]]
is vast, it isn't complete. Aside from #63 (WG21-P1771 [[nodiscard]]
For Constructors), there are Standard functions that we haven't marked, like the operator functors (classic and transparent):
Lines 48 to 57 in 447f879
Lines 156 to 166 in 447f879
We also haven't comprehensively marked our internal helper functions.
For reference, we use the following criteria for marking Standard functions:
- Pure observers. (This is the most common reason.) If a function has no side effects, and simply inspects state in order to return a value, then discarding that value is extremely likely to be a bug. For example,
vector::size()
is a pure observer. This also includes functions that are pure observers except in rare cases when user-provided machinery has side effects. For example, we can consider thecount_if()
algorithm to be a pure observer, even though the user-provided function object might have side effects. - Functions that return raw resources. (Usually raw pointers to freshly allocated memory.) Discarding such return values is extremely likely to be a resource/memory leak.
- Functions where discarding the return value is extremely likely to be a correctness bug. This is uncommon; good examples are the
remove()
,remove_if()
, andunique()
algorithms (where the return value should be passed to a containererase()
call).
Because we mark so many functions as [[nodiscard]]
, it's possible for a large project to encounter many warnings when upgrading their toolset and STL. We want all of these warnings to indicate actual bugs in user code (or at least unnecessary function calls that should be removed), with virtually no false positives. To preserve the high quality of these warnings, we avoid marking functions as [[nodiscard]]
when they can be used correctly while discarding, even if such use is uncommon (e.g. 10% of calls). Currently, the best example is unique_ptr::release()
. Discarding this is likely to be a resource/memory leak, but not always - e.g. ownership of the raw pointer may have been previously handed off via unique_ptr::get()
. Ideally, we could mark unique_ptr::release()
as [[nodiscard]]
, and that 10% of valid calls could add (void)
casts to indicate that they're doing something unusual. However, we have currently chosen not to mark it, prioritizing the quality of this warning over detecting bogus code here. This similarly applied to condition_variable::wait_for()
and condition_variable::wait_until()
, where valid algorithms can discard the return values. Fortunately, tricky scenarios like these are rare.
For another example, <charconv>
from_chars()
and to_chars()
aren't marked [[nodiscard]]
. While they return from_chars_result
and to_chars_result
containing an errc
and a pointer, which should typically be checked, it is possible to call these functions knowing that they'll succeed and how much they'll read/write. Perhaps someday we could have multiple levels of [[nodiscard]]
strictness, but for now, we aren't marking these functions.
For internal helper functions, we can and should be much more aggressive about [[nodiscard]]
.
After WG21-P2422R1, the Standard no longer depicts any functions as [[nodiscard]]
. We're still allowed to be awesome.