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.