Why not apply [[nodiscard]] to every constructor?

1.4k Views Asked by At

Since C++20, [[nodiscard]] can be applied to constructors. http://wg21.link/p1771 has the example:

struct [[nodiscard]] my_scopeguard { /* ... */ };
struct my_unique {
  my_unique() = default;                                // does not acquire resource
  [[nodiscard]] my_unique(int fd) { /* ... */ }         // acquires resource
  ~my_unique() noexcept { /* ... */ }                   // releases resource, if any
  /* ... */
};
struct [[nodiscard]] error_info { /* ... */ };
error_info enable_missile_safety_mode();
void launch_missiles();
void test_missiles() {
  my_scopeguard();              // warning encouraged
  (void)my_scopeguard(),        // warning not encouraged, cast to void
    launch_missiles();          // comma operator, statement continues
  my_unique(42);                // warning encouraged
  my_unique();                  // warning not encouraged
  enable_missile_safety_mode(); // warning encouraged
  launch_missiles();
}
error_info &foo();
void f() { foo(); }             // warning not encouraged: not a nodiscard call, because neither
                                // the (reference) return type nor the function is declared nodiscard

Usually constructors have no side effects. So discarding the result is pointless. For example, discarding std::vector as below is pointless:

std::vector{1,0,1,0,1,1,0,0};

It would be useful if std::vector constructor is [[nodiscard]], so that the above code produced a warning.

Notable constructors that do have side effects are lock constructors, like unique_lock or lock_guard. But then those are good target to be marked as [[nodiscard]] as well, to avoid missed scope, like here:

std::lock_guard{Mutex};
InterThreadVariable = value; // ouch, not protected by mutex

It would be useful if std::lock_guard constructor is [[nodiscard]], so that the above code produced a warning.

Sure there's a case like return std::lock_guard{Mutex}, InterThreadVariable;. But it is rare enough to still have [[nodiscard]] guards, and to suppress them locally like return ((void)std::lock_guard{Mutex}, InterThreadVariable);

So, is there any case when a constructor should not be nodiscard?

3

There are 3 best solutions below

2
On BEST ANSWER

An example from the pybind11 library: To wrap a C++-class for python, you do:

PYBIND11_MODULE(example, m) {
    py::class_<MyClass>(m, "MyClass");  // <-- discarded.
}
0
On

If the constructor does not have side effects then it's not worth the effort and code clutter in my opinion. You will notice pretty quickly that you forgot to name that vector in your example.

For the few constructors with side effects there is now [[nodiscard]] available. (which is good)

Since your question is about why not change the default and mark the exceptional case with [[may_discard]] (basically the definition of C++ seems to be all wrong defaults everywhere), this would break backwards compatibility. Such a break is usually only accepted in rare cases, where existing things are harmful and if there is a replacement. (deprecation of std::auto_ptr comes to mind)

There are initiatives to fix this in a different manner. One example is cppfront which is a personal experiment of Herb Sutter. It defines a new syntax which gets all those defaults right and translates them to regular C++ which can then be compiled. You can check out his CppCon 2022 keynote where he demonstrates this: Can C++ be 10x Simpler & Safer?

0
On

When you have a class with a deleted constructor. I see no point in marking it [[nodiscard]].