Skip to content

STL: Finish marking functions as _NODISCARD #206

Open

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):

STL/stl/inc/xstddef

Lines 48 to 57 in 447f879

template <class _Ty = void>
struct plus {
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty first_argument_type;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty second_argument_type;
_CXX17_DEPRECATE_ADAPTOR_TYPEDEFS typedef _Ty result_type;
constexpr _Ty operator()(const _Ty& _Left, const _Ty& _Right) const {
return _Left + _Right;
}
};

STL/stl/inc/xstddef

Lines 156 to 166 in 447f879

template <>
struct plus<void> {
using is_transparent = int;
template <class _Ty1, class _Ty2>
constexpr auto operator()(_Ty1&& _Left, _Ty2&& _Right) const
noexcept(noexcept(static_cast<_Ty1&&>(_Left) + static_cast<_Ty2&&>(_Right))) // strengthened
-> decltype(static_cast<_Ty1&&>(_Left) + static_cast<_Ty2&&>(_Right)) {
return static_cast<_Ty1&&>(_Left) + static_cast<_Ty2&&>(_Right);
}
};

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 the count_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(), and unique() algorithms (where the return value should be passed to a container erase() 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    enhancementSomething can be improved

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions