Aganju
Aganju

Reputation: 6405

Compiler reports 'deleted' operator = , but it is there

I ran into a nasty issue, where the compiler claims an operator= is deleted, but it is there. After several hours of trying around, I produced a minimal solution that reproduces the issue. I am using MSVC Community Edition 2017 15.7.5 (the newest as of today, 2018-07-20), and have set it to 'C++17'

The code is not trivial; the concept is that a template class TT is used to enforces the existence of a static member function foo in a group of classes Fn. This is very similar to a factory pattern, just this solution doesn't create class instances, but reports static detail info about the class.

The error is reported on the assignment in the last line, and reads (full error list at the bottom):

"error C2280: 'C &C::operator =(const C &)': attempting to reference a deleted function"

But line 5 defined this operator, right with those decorators?

The assignment that fails tries to assign a returned const std::vector<C>& to a class member variable.
I thought the const is producing the issue, but removing all of the const in each function does not make any difference; same error in the same line.

Question: Why does the compiler report this, and what is a possible fix?
I think this must be something silly I miss, but I can't find it.

#include <vector>

class C
{
public:
  C(int ii) : i(ii) {}
  C& operator=(const C&) = default;    /// HERE is the assignment operator
  const int i;
};
typedef std::vector<C> CVec;   // shorthand for a vector of C's

template <class T>   // this template forces classes F1, F2, ... to have a static member function 'foo'
class TT {
public:
  static const CVec& foo(void) { return T::foo(); }
};

class F1  // one of many Fn classes
{
public:
  static const CVec& foo(void) { static CVec cv{ C{ 1 }, C{ 2 } }; return cv; }    // static member as forced by template
  //...
};
class F2  // another one of many Fn classes
{
public:
  static const CVec& foo(void) { static CVec cv{ C{ 3 } }; return cv; }     // static member as forced by template
  //...
};

class D    // controller class
{
public:
  CVec cv;
  const CVec& bar(int z)   // function to select one of the subclasses
  {
    switch (z)
    {
      case 1: return TT<F1>::foo();
      case 2: return TT<F2>::foo();
        //...
    }
  }

  void foobar(void)  //selector (from user input)
  {
    int z = 2; // user input
    cv = bar(z);   // THIS assignment produces the error
  }
};

Full Error Text:

------ Build started: Project: BG, Configuration: Debug Win32 ------
bgcore.cpp
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\xutility(2443): error C2280: 'C &C::operator =(const C &)': attempting to reference a deleted function
d:\projects\bg\core\bgcore.h(8): note: see declaration of 'C::operator ='
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\xutility(2462): note: see reference to function template instantiation '_OutIt std::_Copy_unchecked1<_InIt,_OutIt>(_InIt,_InIt,_OutIt,std::_General_ptr_iterator_tag)' being compiled
        with
        [
            _OutIt=C *,
            _InIt=C *
        ]
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\vector(1430): note: see reference to function template instantiation '_OutIt *std::_Copy_unchecked<_Iter,C*>(_InIt,_InIt,_OutIt)' being compiled
        with
        [
            _OutIt=C *,
            _Iter=C *,
            _InIt=C *
        ]
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\vector(1448): note: see reference to function template instantiation 'void std::vector<C,std::allocator<_Ty>>::_Assign_range<_Iter>(_Iter,_Iter,std::forward_iterator_tag)' being compiled
        with
        [
            _Ty=C,
            _Iter=C *
        ]
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\vector(1448): note: see reference to function template instantiation 'void std::vector<C,std::allocator<_Ty>>::_Assign_range<_Iter>(_Iter,_Iter,std::forward_iterator_tag)' being compiled
        with
        [
            _Ty=C,
            _Iter=C *
        ]
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\vector(1471): note: see reference to function template instantiation 'void std::vector<C,std::allocator<_Ty>>::assign<C*,void>(_Iter,_Iter)' being compiled
        with
        [
            _Ty=C,
            _Iter=C *
        ]
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\vector(1471): note: see reference to function template instantiation 'void std::vector<C,std::allocator<_Ty>>::assign<C*,void>(_Iter,_Iter)' being compiled
        with
        [
            _Ty=C,
            _Iter=C *
        ]
c:\program files (x86)\microsoft visual studio\2017\community\vc\tools\msvc\14.14.26428\include\vector(1457): note: while compiling class template member function 'std::vector<C,std::allocator<_Ty>> &std::vector<_Ty,std::allocator<_Ty>>::operator =(const std::vector<_Ty,std::allocator<_Ty>> &)'
        with
        [
            _Ty=C
        ]
d:\projects\bg\core\bgcore.h(49): note: see reference to function template instantiation 'std::vector<C,std::allocator<_Ty>> &std::vector<_Ty,std::allocator<_Ty>>::operator =(const std::vector<_Ty,std::allocator<_Ty>> &)' being compiled
        with
        [
            _Ty=C
        ]
d:\projects\bg\core\bgcore.h(22): note: see reference to class template instantiation 'std::vector<C,std::allocator<_Ty>>' being compiled
        with
        [
            _Ty=C
        ]
Done building project "BG.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========

Upvotes: 2

Views: 199

Answers (3)

doug
doug

Reputation: 4299

As has been pointed out, you can't default an assignment-copy constructor that has been deleted because a member is const. This is because there was no way to change the object in c++17 or earlier. You could write an assignment-copy constructor but there was still no way to copy the const member.

That changed in c++ 20 and you can now legally do it.

If you change

C& operator=(const C&) = default;    /// HERE is the assignment operator

to

C& operator=(const C& from)
{
if (this!= &from)
{
   std::destroy_at(this);
   std::construct_at(this, from);
}

Upvotes: 0

Barry
Barry

Reputation: 304092

Here's a much shorter version of your issue:

class C
{
public:
  C(int ii) : i(ii) {}
  C& operator=(const C&) = default;
  const int i;
};

C a(1);
a = a; // error: use of deleted function

While you did default the function, that doesn't mean that it's necessarily valid. It just means your explicitly defaulting it. A defaulted copy assignment operator will copy-assign all of the subobjects and members one by one. But your one member is a const int, and you cannot copy-assign that! It's const!

The specific rule is in [class.copy.assign]/7:

A defaulted copy/move assignment operator for class X is defined as deleted if X has: [...] a non-static data member of const non-class type (or array thereof), or [...]

Make the member just int i, and you're fine.

Upvotes: 6

Caleth
Caleth

Reputation: 63227

You are always allowed to = default a special member function. This facilitates obeying the rule of zero or five. It makes it clear to the reader that you have considered that member, and want the default behaviour.

Upvotes: 0

Related Questions