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
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
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
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
[[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
Prevents Silent Errors: Compiler warnings catch mistakes where return values are accidentally ignored
Documents Intent: Makes it explicit that the return value carries meaningful information
Improves API Design: Guides users toward correct usage patterns, especially for:
- Immutable objects
- Fluent interfaces
- Error-prone operations
Enhances Const Correctness: Complements the use of
const
methods by preventing meaningless calls to pure functions
Best Practices
Follow Tool Guidance: When static analyzers (like clang-tidy) suggest
[[nodiscard]]
for a function, evaluate if ignoring its return could cause issuesPrioritize High-Impact Cases:
Suppress Judiciously: If you intentionally need to discard a return value:
cppvoid test() { static_cast<void>(potentiallyUnusedResult()); // Silence warning }
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.