0

This is probably not specific to C++20, but that's what I'm using right now. I have a simple struct

struct foo {
    int bar;
}

which can be declared and initialized as

const auto baz = foo{42};

Now, when I disable the move constructor (foo(foo&&) = delete;), the above initialization fails with

error: no matching function for call to ‘foo::foo()’

What's the reason for getting this error and is there a way to bring back the default behavior?

Azad Salahli
  • 876
  • 3
  • 14
  • 30

1 Answers1

5

It is specific to C++20. Since C++20 a class is no longer aggregate if it there is any user-declared constructor at all, even if it is only defaulted or deleted. Aggregate initialization won't work anymore.

This is a backwards-compatibility breaking change and you can't really get back the old behavior. You will have to adjust your code to the change.

If you want to disable the move constructor, you need to add a proper constructor to use for the initialization, i.e. one taking an int as argument and initializing bar with it, and you can't rely on aggregate initialization anymore. Alternatively you can add a non-movable member with default initializer as last member of the class. Then it won't be required to be provided an initializer in the aggregate-initialization, but will make the whole class non-movable.

It is however a bit surprising that you need to disable the move constructor manually at all. If the class is aggregate without any declared constructors it will be movable if and only if all its contents are movable. That should usually be the expected behavior as an aggregate class just lumps together the individual members without adding any further class invariants.

user17732522
  • 53,019
  • 2
  • 56
  • 105
  • Thanks! This is kind of a learning excercise for me. The `bar` there was a file descriptor and I had `foo`'s destructor call `close()` on it. Hence wanting to disable copy and move constructors. – Azad Salahli Aug 24 '22 at 22:52
  • @AzadSalahli If `foo` is responsible for closing the descriptor, then it should also be responsible for opening it. `foo` should then not be aggregate, but instead have the file descriptor private and have constructors to open the descriptor. – user17732522 Aug 24 '22 at 22:55
  • 1
    @AzadSalahli Also, such a class managing a resource should typically have a deleted _copy_ constructor, but have the move constructor defined in such a way that it can transfer ownership to another instance, similar to how `std::unqiue_ptr` works. That comes at the cost of having to designate one specific empty state of the class, which sometimes may be too much memory overhead to make it worthwhile though. – user17732522 Aug 24 '22 at 22:59
  • With respect to `foo` getting already opened file descriptor, I was in fact mirroring `unique_ptr`. You can initialize `unique_ptr` with an existing pointer. Point taken about implementing a proper move constructor. With file descriptors, I can just have `-1` as an empty value. – Azad Salahli Aug 24 '22 at 23:03
  • @AzadSalahli Yes, true. I forgot to add that possibility. The point is though, that there should be a constructor that clearly takes ownership of the resource (which is why it should also probably be `explicit`). Having a public member muddies the ownership relations a lot in my opinion. – user17732522 Aug 24 '22 at 23:08