DEV Community

llzzz
llzzz

Posted on Edited on AI-assisted

C++ `[[nodiscard]]`: A Complete Guide (C++17 to C++26)

[[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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

The same logic applies to enums:

enum [[nodiscard]] Status { Ok, Failed };

Status check(){ return Status::Ok; }
int main() { check(); }  // warning
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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(); }
Enter fullscreen mode Exit fullscreen mode

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
}
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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, bool success 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)