Why Is Std::Move Not [[Nodiscard]] in C++20

Why is std::move not [[nodiscard]] in C++20?

AFAIK P0600R1 is the only proposal for adding [[nodiscard]] to the standard library that was applied to C++20. From that paper:

We suggest a conservative approach:

[...]

It should not be added when:

  • [...]
  • not using the return value makes no sense but doesn’t hurt and is usually not an error
  • [...]

So, [[nodiscard]] should not signal bad code if this

  • [...]
  • doesn’t hurt and probably no state change was meant that doesn’t happen

So the reason is that the standard library uses a conservative approach and a more aggresive one is not yet proposed.

Why does std::forward_list::empty has [[nodiscard]] while std::forward_list::max_size doesn't?

The reason is two-part:

  1. There is no way to confuse the query "what is the maximum size?" expressed as .maximum_size() with anything else, while you could confuse the query "is it empty?" expressed as .empty() with the command "empty it!", which got the name .clear().

  2. [[nodiscard]] is new, and has not been applied everywhere applicable in the standard library (to date). C++20 adds some places, but still isn't anywhere near comprehensive.

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

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.
}

Why does [[nodiscard]] only encourage compiler to issue a warning and does not require it?

The C++ standard specifies the behavior of a valid C++ program. In so doing, it also defines what "valid C++ program" means.

Diagnostics are only required for code which is ill-formed, code which is syntactically or semantically incorrect (and even then, there are some ill-formed circumstances that don't require diagnostics). Either the code is well-formed, or it is ill-formed and (usually) a diagnostic is displayed.

So the very idea of a "warning" is just not something the C++ standard recognizes, or is meant to recognize. Notice that even the "implementations are encouraged to issue a warning" statement is in a non-normative notation, rather than a legitimate specification of behavior.

Why is unique_ptr::release not defined with [[nodiscard]]?

This is addressed in the paper that added [[nodiscard]] to many of the functions. From P0600R1 this is the remark about adding [[nodiscard]] to unique_ptr::release()

Titus: at Google 3.5% of calls would fail, but analysis showed
that it was correct (but weird ownership semantics). See
reflector email.

Is nodiscard necessary on operators?

Let me cite the following paper by N.Josuttis: "[[nodiscard]] in the library" (with some omissions, see the full paper):

C++17 introduced the [[nodiscard]] attribute. The question is, where to apply it now in the standard library. It should be added where:

  • not using the return value always is a “huge mistake” (e.g. always resulting in resource leak),
  • not using the return value is a source of trouble and easily can happen (not obvious that something is wrong).

It should not be added when:

  • not using the return value is a possible/common way of programming at least for some input,
  • not using the return value makes no sense but doesn’t hurt and is usually not an error.

So, [[nodiscard]] should not signal bad code if this

  • can be useful not to use the return value,
  • is common not to use the return value,
  • doesn’t hurt and probably no state change was meant that doesn’t happen.

c++20: how to move capture a class instance

IMO, the reason is that the type lambda you passed to std::function ctor violate std::function type requirement, which needs the passed Callable must be CopyConstructible. Since the captured type A's copy constructor is deleted, it results that the lambda's copy constructor cannot be implicitly-declared.

Here's some reference from cppreference page:


  1. Initializes the target with std::forward<F>(f). The target is of type std::decay<F>::type. If f is a null pointer to function, a null pointer to member, or an empty value of some std::function specialization, *this will be empty after the call. This constructor does not participate in overload resolution unless the target type is not same as function, and its lvalue is Callable for argument types Args... and return type R. The program is ill-formed if the target type is not copy-constructible or initialization of the target is ill-formed.

Also from the same page:

std::decay<F>::type must meet the requirements of Callable and CopyConstructible.



Possible solution 1

If c++23 is acceptable to you, you can directly use std::move_only_function. https://godbolt.org/z/qhfd83955

Possible solution 2

You can write a custom naive function wrapper to handle your move only type, which is a simple type-erasure class with a function call operator, basically the same thing as chapter 9 of book "C++ Concurrency In Action":

class TaskWrapper {
struct impl_base {
virtual void call() = 0;
virtual ~impl_base() = default;
};
std::unique_ptr<impl_base> impl;
template <typename F>
struct impl_type : impl_base {
F f;
impl_type(F&& f_) : f(std::move(f_)) {}
void call() final { f(); }
};

public:
template <typename F>
TaskWrapper(F&& f) : impl(new impl_type<F>(std::forward<F>(f))) {}
void operator()() { impl->call(); }
TaskWrapper() = default;
TaskWrapper(TaskWrapper&& other) noexcept : impl(std::move(other.impl)) {}
TaskWrapper& operator=(TaskWrapper&& other) noexcept {
impl = std::move(other.impl);
return *this;
}
TaskWrapper(const TaskWrapper&) = delete;
TaskWrapper(TaskWrapper&) = delete;
TaskWrapper& operator=(const TaskWrapper&) = delete;
};

And change B signature to:

class B {
public:
explicit B(TaskWrapper cb) { (void)(cb); };
};

Then use it like:

B b_factory() {
auto a { A() };
return B {
TaskWrapper([a = std::move(a)]() {
// do something with a
})
};
}

What is std::move(), and when should it be used?

Wikipedia Page on C++11 R-value references and move constructors

  1. In C++11, in addition to copy constructors, objects can have move constructors.

    (And in addition to copy assignment operators, they have move assignment operators.)
  2. The move constructor is used instead of the copy constructor, if the object has type "rvalue-reference" (Type &&).
  3. std::move() is a cast that produces an rvalue-reference to an object, to enable moving from it.

It's a new C++ way to avoid copies. For example, using a move constructor, a std::vector could just copy its internal pointer to data to the new object, leaving the moved object in an moved from state, therefore not copying all the data. This would be C++-valid.

Try googling for move semantics, rvalue, perfect forwarding.



Related Topics



Leave a reply



Submit