2

I'm familiar with the principle (for example, from this answer and this one) that when a class has a move constructor and/or move assignment operator, its default copy constructor and copy assignment operator are deleted. However, In the examples I've seen, this can be addressed by explicitly defining a new copy constructor and assignment operator.

In my particular case, I have a class which is derived by joint inheritance from a C-style struct and a template class. The copy and move assignment operators are explicitly defined in the template, whilst the copy and move constructors are explicitly defined in the class itself. In other words, everything is defined explicitly, though not all in the same place. Here is some example code:

typedef struct {
    int n;
} myStruct;

template <typename T> class myTemplate
{
public:
    // Default constructor
    myTemplate<T>() : t_n(nullptr) {}

    // Cannot create copy or move constructors in template, as cannot 
    // access the 'n' member directly

    // Copy assignment operator
    myTemplate<T> & operator=(const myTemplate<T> &source)
    {
        if (this != &source)
        {
            *t_n = *(source.t_n);
        }
        return *this;
    }

    //! Move assignment operator
    myTemplate<T> & operator=(myTemplate<T> &&source)
    {
        if (this != &source)
        {
            *t_n = *(source.t_n);
            *(source.t_n) = 0;
            source.t_n = nullptr;
        }
        return *this;
    }

    T* t_n;
};

class myClass : public myStruct, public myTemplate<int>
{
public:
    // Default constructor
    myClass() : myTemplate<int>()
    {
        n = 0;
        t_n = &n;
    }

    // Alternative constructor
    myClass(const int &n_init) : myTemplate<int>()
    {
        n = n_init;
        t_n = &n;
    }

    // Copy constructor
    myClass(const myClass &source) : myTemplate<int>()
    {
        n = source.n;
        t_n = &n;
    }

    // Move constructor
    myClass(myClass &&source) : myTemplate<int>()
    {
        n = source.n;
        t_n = &n;
        source.n = 0;
        source.t_n = nullptr;
    }
};

int main()
{

    myClass myObject(5);
    myClass myOtherObject;

    // Compilation error here:
    myOtherObject = myObject;

    return 1;
}

In Visual C++ and Intel C++ on Windows, this works exactly as I'd expect it to. On gcc 4.9.0 in Linux, however, I get the dreaded error message:

g++ -c -std=c++11 Main.cppMain.cpp: In function ‘int main()’:
Main.cpp:78:19: error: use of deleted function ‘myClass& myClass::operator=(const myClass&)’
     myOtherObject = myObject;
               ^
Main.cpp:39:7: note: ‘myClass& myClass::operator=(const myClass&)’ is implicitly declared as deleted because ‘myClass’ declares a move constructor or move assignment operator
 class myClass : public myStruct, public myTemplate<int>

Sure enough, the error goes away if I define an explicit copy assignment operator in the class itself, rather than in the template, but that's bothersome to do and undermines the advantage of using the template, since (a) my actual copy assignment operator is a lot bigger than the one shown here and (b) there are a large number of different classes that all share this template.

So, is this simply a bug in gcc 4.9.0, or is this in fact what the standard says should happen?

Community
  • 1
  • 1
Eos Pengwern
  • 1,467
  • 3
  • 20
  • 37

1 Answers1

5

GCC is correct (and Clang and EDG agree).

myTemplate has a user-declared move constructor, therefore its copy assignment operator is deleted.

You've provided a copy constructor, but not copy assignment operator. Just declare a copy assignment operator for myTemplate and define it as defaulted. That takes one extra line of code.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • That is the answer (as also given by @Howard Hinnant in his comment two minutes previously). I've learned some new C++ today, as I'd never heard of 'defaulting an operator' before. I added the two lines "myClass & operator=(const myClass &source) = default;" and "myClass & operator=(myClass &&source) = default;" into my class definition, and everything worked as expected. I still think it's weird, though. – Eos Pengwern Aug 11 '14 at 15:47
  • @EosPengwern: Now for the bad news. Visual C++ does not yet implement defaulted move members... – Howard Hinnant Aug 11 '14 at 15:52
  • @Howard Hinnant; too right, although the Intel C++ compiler doesn't complain. I need to wrap an #IFNDEF _MSC_VER around the second declaration and all of a sudden my carefully-designed templated code is looking like a dog's breakfast! – Eos Pengwern Aug 11 '14 at 20:10