lefticus / cpp_weekly

The official C++ Weekly Repository. Code samples and notes of future / past episodes will land here at various times. PR's will be accepted in some cases.
The Unlicense
683 stars 26 forks source link

The confusing way moves are broken #402

Open lefticus opened 1 month ago

lefticus commented 1 month ago
struct S {
  ~S() {}
};

The above type does not have a move constructor or move assignment, but it still works with move operations and is_move_constructible returns true.

So how do we show that move operations don't exist?

https://compiler-explorer.com/z/dva81543s

#include <utility>
#include <type_traits>

struct Contained {
  Contained();
  Contained(const Contained &);
  Contained(Contained &&);
  Contained &operator=(const Contained &);
  Contained &operator=(Contained &&);
  ~Contained();
};

struct S {
  Contained c;
  // comment this out and see the difference
  ~S();
};

S getS();

static_assert(std::is_move_constructible_v<S>);
static_assert(std::is_move_assignable_v<S>);

void useS(S);

int main()
{
  S obj;
  obj = getS();
  useS(std::move(obj));
}
LB-- commented 1 month ago

That's interesting, though it makes sense why it's done that way now that I think about it. I always find it confusing to remember the rules for when and how the compiler automatically generates special member functions, and the table mapping it out is messy. I wonder when you would actually ever care about is_move_constructible_v/is_move_assignable_v though, since normally you would only check is_nothrow_move_constructible_v/is_nothrow_move_assignable_v. When the move operations are correctly noexcept, those type traits still report false in the above example because they detect that the copy operations are not noexcept. Therefore using the nothrow versions helps you catch this issue and/or deal with it appropriately.