6

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?

Alex Guteniev
  • 12,039
  • 2
  • 34
  • 79
  • 1
    I have a small test runner which I setup to be used as a temporary that is a self contained threadpool that starts executing provided tests in parallel the moment it goes out of scope. I don't want it to produce a warning just because I didn't give a name to an instance of such a class. Other than that, I think this is very opinion-based, just like whether what I'm doing is great or not. The thing is, most valid use cases of a class don't result in its instance being discarded in the first place, and when they do, its most likely intended. This comment is about general classes, not errors. – Kaihaku Sep 19 '21 at 09:07
  • One can instantiate an anonymous object whose lifespan is limited by a the expression, like that lock_guard. Declare its ctor nodiscard, prepare a surprise for you library's users. – bipll Sep 19 '21 at 09:08
  • In any case, a somewhat softer solution would be to introduce a compiler flag that sets all constructors to nodiscard. Would help if you have code fragments that were written without C++20 in mind. – Aziuth Sep 19 '21 at 09:09
  • @Kaihaku, good point about some process contained in an object as an example of side-effect constructor that may be discarded. Rather rare, but still valid case. @bipll, I've added a comment on expression `lock_guard`. – Alex Guteniev Sep 19 '21 at 09:15
  • @Aziuth, that's the point of the question, why mark `[[nodiscard]]` constructors, and not do the opposite and introduce like `[[may_discard]]` attribute for exceptional cases. – Alex Guteniev Sep 19 '21 at 09:16

3 Answers3

4

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.
}
unddoch
  • 5,790
  • 1
  • 24
  • 37
  • Maybe. But I think that this will expose a class without methods, so it may be worth declaring `[[nodiscard]]` anyway. – Alex Guteniev Sep 19 '21 at 12:11
  • 2
    True, but some classes (like tags) don't have any members or member methods. Or maybe you don't need them exposed to the python code. – unddoch Sep 19 '21 at 12:14
1

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 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?

Brandlingo
  • 2,817
  • 1
  • 22
  • 34
1

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

303
  • 2,417
  • 1
  • 11
  • 25