Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Code unexpectedly fails to compile. Why?

Take a look a the following code example which uses class uncopiable similar to boost::noncopyable:

#include <vector>

class uncopiable {
    using self = uncopiable;
protected:
    uncopiable() {}
    ~uncopiable() {}
    uncopiable(const self&) = delete;
    self& operator=(const self&) = delete;
};

struct A {
    struct B : uncopiable {
        using self = B;

        B() {
        }

        B(B&&) = default;
        self& operator=(B&&) = default;

        ~B() {
        }
    };

    A() { v.emplace_back(); }
    ~A() {}

private:
    std::vector<B> v;
};

int main () {}

Since I wanted to make inner class move only I explicitly specified its move constructor and assignment operator to be default ones but also since I've heard that it's a good practice to specify all of the "special member functions" in such case I inherited it from uncopiable. The problem is that compilation fails with every compiler and something similar to the following error message is displayed (this message is excerpt from the clang one):

/usr/include/c++/v1/memory:1645:31: error: call to implicitly-deleted copy constructor of 'A::B'

...

main.cpp:26:10: note: in instantiation of function template specialization 'std::__1::vector >::emplace_back<>' requested here

main.cpp:19:3: note: copy constructor is implicitly deleted because 'B' has a user-declared move constructor

It could be fixed by removing inheritance (copy operations would still not be created). But writing copy operations to be explicitly deleted inside class after that is also okay.

My questions are: why does it happen? Could it be considered a deficiency of disabling constructors/assignment operators through inheritance of helper classes?

like image 641
Predelnik Avatar asked Dec 31 '25 21:12

Predelnik


1 Answers

The problem is that your uncopiable class is not moveable. Therefore the default move constructor / assignment operator of the derived class try to use the deleted copy versions.

static_assert(std::is_move_constructible<uncopiable>::value, "");  // fails
static_assert(std::is_move_assignable<uncopiable>::value, "");     // fails

The reason for this is § 12.8 ¶ 9:

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,
  • X does not have a user-declared copy assignment operator,
  • X does not have a user-declared move assignment operator, and
  • X does not have a user-declared destructor.

Declaring a copy operator or assignment operator as deleted still counts as declaring it.

The solution is of course to declare the move operations for uncopiable.

uncopiable(uncopiable&&) noexcept = default;
uncopiable& operator=(uncopiable&&) noexcept = default;

Note that the move operations should usually be declared noexcept. Especially if you want to use the type in a std::vector like in your example.

like image 190
5gon12eder Avatar answered Jan 02 '26 12:01

5gon12eder