3

I apologize for the wordy title, but I'm having trouble concisely expressing this question.

I have a class with a deleted copy constructor and copy assignment operator. When I attempt to initialize an array with instances of the class I get a compiler error unless I provide a move constructor. However, the provided move constructor is not actually invoked.

Here's a MWE illustrating the issue.

#include <cstdio>
#include <string>

class A
{
public:
    explicit A(std::string s) : s_(s) { puts("constructor"); }
    A() = delete;
    ~A() = default;
    A(const A &other) = delete;
    A &operator=(const A &other) = delete;
    // A(A &&other) : s_(std::move(other.s_)) { puts("move constructor"); }

    void print_s() { printf("%s\n", s_.c_str()); }

private:
    std::string s_;
};

int main()
{
    A arr[]{A{"some"}, A{"string"}};
    for (auto &a : arr) {
        a.print_s();
    }
}

With the move constructor commented out as shown, I get the error:

> g++ file.cc -g -std=c++11 -o file && ./file
file.cc: In function ‘int main()’:
file.cc:22:32: error: use of deleted function ‘A::A(const A&)’
   22 |  A arr[]{A{"some"}, A{"string"}};
      |                                ^
file.cc:10:2: note: declared here
   10 |  A(const A &other) = delete;
      |  ^
file.cc:22:32: error: use of deleted function ‘A::A(const A&)’
   22 |  A arr[]{A{"some"}, A{"string"}};
      |                                ^
file.cc:10:2: note: declared here
   10 |  A(const A &other) = delete;
      |  ^

If I uncomment the move constructor I get the output

constructor
constructor
some
string

So, the move constructor does not actually appear to be invoked.

Why is the move constructor needed? Have I made a mistake somewhere (e.g., a poorly implemented move constructor)?

NathanOliver
  • 171,901
  • 28
  • 288
  • 402
MattHusz
  • 452
  • 4
  • 15
  • 3
    The compiler is allowed to optimize away move/copy operations. So your class may need the operation to be valid to define a valid program but once the code is valid the copiler can optimize away the move/copy construction. – Martin York Jul 29 '21 at 21:11
  • @MartinYork so initializing the array this way is a "move" operation for the array's elements, not a direct initialization? Is there any way to directly initialize the elements? – MattHusz Jul 29 '21 at 21:13

1 Answers1

6

With

A arr[]{A{"some"}, A{"string"}};

You are creating two temporary A objects and then copying/moving them into the array. Now, the compiler can optimize this copy/move away, but that class still needs to be copyable/moveable in order for this to happen. When you comment out your move constructor, the class is no longer copyable or moveble, so you get a compiler error.

It should be noted that this changed in C++17 with its guaranteed copy elision. The code will compile in C++17 and beyond for types that can't be copied or moved.


If you change

explicit A(std::string s) : s_(s) { puts("constructor"); }

to

A(std::string s) : s_(s) { puts("constructor"); }

then your main function could become

int main()
{
    A arr[]{{"some"}, {"string"}};
    for (auto &a : arr) {
        a.print_s();
    }
}

which will compile even if you can't copy or move an A.

NathanOliver
  • 171,901
  • 28
  • 288
  • 402