Skip to content

C++ [[nodiscard]] Attribute

Problem Statement

When working with const member functions that return computed values without side effects, developers might unintentionally write code that ignores the return value. This creates a problem because:

  • The computation essentially becomes a no-op
  • Potential errors slip through silently
  • The original design intent of the function isn't enforced

The confusion arises when IDEs like CLion suggest adding the [[nodiscard]] attribute to function declarations, leaving developers unsure about:

  • Why return values might be ignored
  • When this attribute should be applied
  • What benefits it provides at compile time

Solution: Preventing Ignored Return Values

What [[nodiscard]] Does

The [[nodiscard]] attribute (introduced in C++17) tells compilers to generate warnings when a function's return value is discarded. This helps enforce correct usage patterns by:

  • Highlighting likely programming errors at compile time
  • Documenting design intentions in code
  • Preventing accidental resource leaks or incorrect operations

When to Apply [[nodiscard]]

Use [[nodiscard]] for:

  • Pure functions (no side effects)
  • Functions returning resources (memory handles, file descriptors)
  • State accessors where ignoring the result leads to logic errors
  • Factory functions and constructors
  • Methods returning modified copies rather than mutating in-place

Avoid Overuse

Don't apply [[nodiscard]] universally. Reserve it for cases where ignoring the return value is unambiguously an error.

Practical Examples

Basic Use Case

cpp
class Calculator {
public:
    [[nodiscard]] int add(int a, int b) const {
        return a + b;
    }
};

int main() {
    Calculator calc;
    calc.add(5, 3);  // Compiler warning!

    int result = calc.add(5, 3);  // Correct
    return 0;
}

Mutating vs. Non-Mutating Operations

cpp
class DateTime {
public:
    [[nodiscard]] DateTime subtractOneDay() const {
        // Returns modified copy
        DateTime result(*this);
        result.days--;
        return result;
    }
};

int main() {
    DateTime current = DateTime::now();
    
    // BAD: Discards return value (compiler warning)
    current.subtractOneDay();  
    
    // GOOD: Uses returned object
    DateTime yesterday = current.subtractOneDay();
}

Collection Operations

cpp
class ImmutableList {
public:
    // Clearly signals non-mutating operation
    [[nodiscard]] ImmutableList reverse() const {
        return /* new reversed list */;
    }
};

void processList() {
    ImmutableList original{1, 2, 3};
    original.reverse();  // Warning: Result discarded!
    
    ImmutableList reversed = original.reverse();  // Correct
}

Real-World Applications

cpp
[[nodiscard]] std::unique_ptr<Resource> createResource() {
    return std::make_unique<Resource>();
}

[[nodiscard]] ErrorStatus saveToDatabase(Data data) {
    /* returns operation status */
}

int main() {
    createResource();  // Warning: Resource immediately leaked!
    saveToDatabase({});  // Warning: Status ignored - potential error
    
    // Correct usage
    auto resource = createResource();
    if (saveToDatabase(data) != ErrorStatus::Success) {
        /* handle error */
    }
}

Key Benefits Explained

  1. Prevents Silent Errors: Compiler warnings catch mistakes where return values are accidentally ignored

  2. Documents Intent: Makes it explicit that the return value carries meaningful information

  3. Improves API Design: Guides users toward correct usage patterns, especially for:

    • Immutable objects
    • Fluent interfaces
    • Error-prone operations
  4. Enhances Const Correctness: Complements the use of const methods by preventing meaningless calls to pure functions

Best Practices

  1. Follow Tool Guidance: When static analyzers (like clang-tidy) suggest [[nodiscard]] for a function, evaluate if ignoring its return could cause issues

  2. Prioritize High-Impact Cases:

  3. Suppress Judiciously: If you intentionally need to discard a return value:

    cpp
    void test() {
        static_cast<void>(potentiallyUnusedResult()); // Silence warning
    }
  4. Combine with Modern C++ Features:

    cpp
    [[nodiscard]] std::expected<Data, Error> parseInput() {
        // Combines nodiscard with C++23 error handling
    }

The [[nodiscard]] attribute provides zero-cost, compile-time protection against a common class of programming errors. By selectively applying it to functions where return values must be handled, you create self-documenting APIs that prevent misuse and reduce debugging time.