C++ nodiscard Attribute: Prevent Ignored Returns
In modern C++, silently discarding the return value of a function can
introduce severe bugs, ranging from unhandled error codes and resource
leaks to dead code that fails to perform intended mutations. Introduced
in C++17 and expanded in C++20, the standard attribute
[[nodiscard]] directly addresses this issue by turning
silent oversights into compile-time diagnostics. This article explains
how [[nodiscard]] works, where it can be applied, how the
compiler enforces it, and how developers can handle cases where a return
value is intentionally ignored.
The Problem of Ignored Return Values
In standard C++, discarding a function's return value is valid syntax:
allocate_memory(); // Return value (pointer or handle) is dropped silently
std::vector<int> v{1, 2, 3};
v.empty(); // Common bug: developer intended v.clear()When functions return error codes, status objects, or freshly allocated resources, dropping the return value leads to silent failures. Compilers historically had no standardized mechanism to distinguish between a function whose return value is optional and one whose return value is critical to program correctness.
How [[nodiscard]] Works
The [[nodiscard]] attribute instructs the compiler to
issue a warning if the return value of a function call is evaluated in a
discarded-value expression.
[[nodiscard]] bool connect_to_database();
void setup() {
connect_to_database(); // Compiler warning: ignoring return value of function declared with 'nodiscard' attribute
}By producing a warning at the call site, the compiler ensures that the caller either stores, inspects, or explicitly acknowledges the result.
Applying [[nodiscard]]
to Types
In addition to individual functions, [[nodiscard]] can
be applied directly to type definitions, such as classes, structs, or
enums. When a type is marked [[nodiscard]], any function
that returns that type by value automatically inherits the nodiscard
behavior:
struct [[nodiscard]] Status {
int error_code;
const char* message;
};
Status perform_transaction();
void run() {
perform_transaction(); // Triggers a compiler warning because 'Status' is [[nodiscard]]
}This pattern is especially valuable for error handling libraries and
types such as std::expected or custom Result
types, ensuring that failure states are never silently bypassed anywhere
in the codebase.
Message Strings in C++20
C++20 enhanced the attribute by allowing an optional string literal
explanation: [[nodiscard("reason")]]. This allows authors
to document precisely why the return value must be checked directly
within compiler error and warning outputs:
[[nodiscard("Ignoring this handle will cause a resource leak.")]]
ResourceHandle acquire_resource();When a developer calls acquire_resource() without
capturing the result, modern compilers display the custom message
alongside the warning.
Legitimate Discards
If a developer consciously chooses to ignore a
[[nodiscard]] return value, they can suppress the warning
explicitly by casting the result to void or assigning it to
std::ignore:
(void)connect_to_database();
// or
std::ignore = connect_to_database();This requirement forces deliberate intent, preventing accidental omissions while retaining flexibility when a return value is genuinely not needed.