[[nodiscard]] is one of the most practical attributes in modern C++. It lets you tell the compiler: "If the caller ignores this return value, please warn them." Since its introduction in C++17, it has grown in power—now applying to functions, types, constructors, and even carrying custom messages. This guide covers every behavior, from the basics to the C++26 _ placeholder.
1. Basic Syntax and Semantics
The attribute has two forms:
[[nodiscard]] // C++17
[[nodiscard("reason string")]] // C++20
It can appear on a function declaration, an enum declaration, or a class declaration .
The core rule: if a discarded-value expression calls a [[nodiscard]] function (or a function returning a [[nodiscard]] type by value), the compiler is encouraged to issue a warning. Casting the result to void is the explicit way to suppress this, signaling intent .
[[nodiscard]] int compute() { return 42; }
int main() {
compute(); // warning: ignoring return value
(void)compute(); // OK: explicit discard
auto x = compute(); // OK: value used
}
2. Applying [[nodiscard]] to Functions
The most common use case. Place it before the function declaration:
[[nodiscard]] bool is_ready();
[[nodiscard]] std::error_code try_open(const char* path);
This is ideal for functions where the return value carries essential information—error codes, success flags, or newly allocated resources.
Important nuance: The warning is encouraged, not guaranteed by the standard. All major compilers (GCC, Clang, MSVC) implement it, but diagnostic details vary . If you can using one of the major compilers, you can pair it with the compiler flag -Werror. Which will turn all the warnings to errors. This is very useful if you want be as rigorous as possible.
3. Applying [[nodiscard]] to Classes and Enums
When you mark a type as [[nodiscard]], any function that returns that type by value triggers the warning if discarded.
struct [[nodiscard]] ErrorInfo
{
ErrorInfo() = default;
};
ErrorInfo do_something()
{
return ErrorInfo{};
} // returns by value
int main() {
do_something(); // warning: discarding a nodiscard type
}
Critical detail: The attribute propagates only through by-value returns. If a function returns a reference to a [[nodiscard]] type, no warning is generated, because the reference doesn't construct a new object that could be "discarded" .
struct [[nodiscard]] ErrorInfo
{
ErrorInfo() = default;
};
ErrorInfo& do_something(ErrorInfo& e)
{
return e;
} // returns by reference
int main() {
ErrorInfo e{};
do_something(e); // no warning: returned by reference
}
The same logic applies to enums:
enum [[nodiscard]] Status { Ok, Failed };
Status check(){ return Status::Ok; }
int main() { check(); } // warning
4. Constructors and Explicit Conversions (C++17 DR, C++20)
Originally, [[nodiscard]] on constructors had no effect. This was fixed via defect report P1771R1, applied retroactively to C++17 .
Now, if a [[nodiscard]] constructor is invoked through explicit type conversion or static_cast, and the resulting object is discarded, a warning may fire:
class Guard {
public:
[[nodiscard]] Guard() {};
};
int main() {
Guard g; // Ok
Guard{}; // warning: temporary discarded
Guard(); // warning: temporary discarded
static_cast<Guard>(Guard{}); // warning if result unused
auto g2 = static_cast<Guard>(Guard{}); // Ok, result used
}
The same applies to initializing an object of a [[nodiscard]] enum or class type via explicit conversion .
5. The C++20 String Message
Since C++20, you can attach a reason string:
[[nodiscard("Check the error code—ignoring it may cause data loss")]]
int write_data() {
return 42;
}
int main() { write_data(); }
This string is unevaluated and typically included verbatim in compiler warnings. It's a documentation feature embedded in diagnostics—useful for library authors .
6. std::ignore: The Intentional Discard
Sometimes you genuinely don't care about a return value, but you still want to avoid warnings. Casting to void works, but std::ignore offers another path:
#include <tuple>
[[nodiscard]] int dont_ignore_me() { return 42; }
int main()
{
std::ignore = dont_ignore_me(); // suppresses nodiscard warning
}
}
std::ignore is an object to which any value can be assigned with no effect. Some coding guidelines recommend it for [[nodiscard]] suppression, though it was originally designed for std::tie tuple unpacking .
Which should you use? (void)expr is the most direct and idiomatic signal of intentional discard. std::ignore = expr is also acceptable and arguably more explicit. Both are recognized by compilers as intentional suppression.
7. C++26: The Unnamed Placeholder _
A long-standing annoyance: if you want to name a variable but ignore its value, you write auto _ = f();. But under -Wunused-variable, the compiler complains that _ is unused. The workaround is [[maybe_unused]] auto _ = f();—verbose and ugly .
C++26 introduces P2169R4, "A Nice Placeholder With No Name." It makes _ a name-independent placeholder usable in most places where an identifier is expected, including structured bindings .
auto _ = f(); // OK in C++26: _ is a placeholder, no unused warning
std::tie(_, inserted) = set.insert("x"); // also works
Critical relationship to [[nodiscard]]: The committee explicitly discussed whether auto _ = f() should suppress [[nodiscard]] warnings. The consensus (and the proposal's direction) is that it should not be treated as a blessed suppression mechanism for [[nodiscard]]. Using _ is a placeholder for unused variables, not a tool for ignoring important return values. The standard [[nodiscard]] semantics remain: assigning to _ still counts as using the value, so no warning is generated—but this is a side effect, not the intended use case .
For intentional suppression, stick with (void) or std::ignore.
8. Best Practices
Do:
- Apply
[[nodiscard]]to pure functions returning computed results (empty(),size(),find()). - Use it on error-handling returns (
std::error_code,boolsuccess flags). - Use the C++20 message for non-obvious cases.
- Mark classes that represent resources or state that must not be dropped (
std::lock_guard-like types).
Don't:
- Apply it to functions with side effects where ignoring is reasonable (logging, notification).
- Use it on every function—warning fatigue reduces its value.
- Rely on
_(C++26) as a[[nodiscard]]escape hatch.
Summary Table
| Context | Syntax | Behavior |
|---|---|---|
| Function | [[nodiscard]] int f(); |
Warns if call result discarded |
| Function (C++20) | [[nodiscard("msg")]] int f(); |
Warning includes "msg"
|
| Class | struct [[nodiscard]] S {}; |
Warns if S returned by value is discarded |
| Enum | enum [[nodiscard]] E {}; |
Same as class |
| Constructor | [[nodiscard]] S(); |
Warns on discarded temporary via explicit conversion |
| Suppression | (void)f(); |
Explicit discard, no warning |
| Suppression | std::ignore = f(); |
Intentional discard via tuple placeholder |
| C++26 | auto _ = f(); |
Placeholder variable; not a [[nodiscard]] suppressor by design |
Top comments (0)