c++c++17nodiscard

What are the common practices and considerations for using the [[nodiscard]] attribute in C++ code?


[[nodiscard]] serves as a warning to the compiler and, subsequently, to developers, indicating that the return value of that function is important and should be used or captured.

What are the common practices and considerations (to detect possible bugs at early stage): To apply [[nodiscard]] attribute to any member function that returns a value (except special cases)? ...Or to apply it only to member functions that it is necessary (for example: a member function that returns a memory allocation)?


Solution

  • When to apply [[nodiscard]] is opinionated.

    Minimal Approch

    The most minimal approach is to only apply it when a function is unusable with the result discarded, and when the user is likely to misuse it without the attribute. This is what the C++ standard library does. For example, std::vector::empty() is marked [[nodiscard]] because it could mean:

    To name another example, std::launder is marked [[nodiscard]] because people commonly assume that it somehow changes the pointer, and they don't need to use the result. [[nodiscard]] is only used in the standard to prevent such misconceptions and very likely mistakes.

    Maximal Approach

    The most maximal approach is to apply [[nodiscard]] whenever a function isn't correctly usable with the result discarded. For example, std::max is pointless if you discard the result, and standard library implementations are free to make it [[nodiscard]]. std::vector::size() could also be made [[nodiscard]] by that standard. In fact, most non-void functions could be(1).

    Clang-Tidy Approach

    You can also stick to the clang-tidy modernize-use-nodiscard rule, which recommends [[nodiscard]] if all of the following conditions are met:

    • no [[nodiscard]], [[noreturn]], __attribute__((warn_unused_result)), [[clang::warn_unused_result]] nor [[gcc::warn_unused_result]] attribute,
    • non-void return type,
    • non-template return types,
    • const member function,
    • non-variadic functions,
    • no non-const reference parameters,
    • no pointer parameters,
    • no template parameters,
    • no template function parameters,
    • not be a member of a class with mutable member variables,
    • no Lambdas,
    • no conversion functions.

    This is obviously quite a long list and fairly pessimistic, but it's a decent starting point and can be automated.

    Conclusion

    You'll have to decide for yourself which approach to choose. There is no official guideline(2). If you don't care about the added verbosity of [[nodiscard]] on a huge amount of functions, it's perfectly fine to use the maximal approach. The more [[nodiscard]] attributes you correctly use, the more bugs you are able to prevent.


    (1) Most, but not all. For example, operator<< overloads for stream insertion should not be [[nodiscard]] because discarding the resulting stream is common practice. The same applies to operator= overloads.

    (2) CppCoreGuidelines also hasn't decided on a recommendation. There is consensus that there should be some guideline for [[nodiscard]], but the discussion got tied up in specifics, and people lost interest in the subject.