The Unterminated String

Embedded Things and Software Stuff

Where's my operator=(const T&)?

Posted at — Jan 31, 2021

Recently, I was using std::transform to juggle some data between structures only to run into a compilation error.

While not the clearest of errors (I was admittedly using an old version of GCC) it indicated my structure was missing an appropriate assignment operator. An assignment operator is not something I typically need to pay much attention to; I’m normally quite happy for the compiler to implicitly generate one for me.

Stripping down the code showed a structure having a member variable of type boost::container::vector prevents the compiler from synthesising an appropriate (const my_struct &) assignment operator. This can be seen by attempting to compile the example below. Indeed, if the type of my_struct.v is changed to std::vector the compiler will generate the appropriate assignment operator and successfully compile the example.

The reason the type boost::container::vector was being favoured was due to it being moveable, even in C++03. (Though there are other advertised benefits to using Boost containers).

#include <boost/container/vector.hpp>
using boost::container::vector;

struct my_struct
{
    vector<int> v;
};

int main()
{
    my_struct a;
    a = my_struct();

    return 0;
}

When the above example is compiled with GCC 4.8.5 and flags -O3 -std=c++03 the compilation error resembles the following:

In function 'int main()':
error: no match for 'operator=' (operand types are 'my_struct' and 'my_struct')
     a = my_struct();
       ^
note: candidate is:
note: my_struct& my_struct::operator=(my_struct&)
 struct my_struct
        ^
note: no known conversion for argument 1 from 'my_struct' to 'my_struct&'

I went looking to the standard to better understand the rules regarding the synthesis of an implicit assignment operator. They can be found in Working Draft, Standard for Programming Language C++, N1905=05-0165, 2005-10-19 section 12.8.10, which states:

If the class definition does not explicitly declare a copy assignment operator, one is declared implicitly.

The implicitly-declared copy assignment operator for a class X will have the form X & X::operator =(const X &) if … for all the non-static data members of X that are of a class type M …, each such class type has a copy assignment operator whose parameter is of type const M&, const volatile M& or M.

Otherwise, the implicitly declared copy assignment operator will have the form X & X::operator =(X &)

But Boost clearly provides a suitable copy assignment operator for boost::container::vector, right? Well, not quite. When compiling Boost for C++03 what you get after preprocessing doesn’t quite line up with the documentation. Boost provides an assignment operator which takes a BOOST_COPY_ASSIGN_REF type, which ultimately ends up looking a little like:

vector& operator=(const ::boost::rv< vector >& x)

This explicit copy assignment will allow a copy from from a vector just fine (via an implicit conversion to boost::rv<vector>). However boost::container::vector doesn’t have an assignment operator which accepts an argument of its own type directly. Therefore, a structure containing a member of type boost::container::vector does not meet the conditions to get an implicitly declared const T & copy assignment operator. The compiler will only synthesise an assignment operator which takes T &.

While an assignment operator has been generated, it cannot handle an rvalue reference. This is the reason for the compilation error in the example and the original code, where the innards of the STL was attempting to assign from the return of the unary operation provided to std::transform:

gcc-4.8.5/include/c++/4.8.5/bits/stl_algo.h: In instantiation of '_OIter std::transform(_IIter, _IIter, _OIter, _UnaryOperation) [with _IIter = boost::container::vec_iterator<input_struct*, false>; _OIter = boost::container::vec_iterator<my_struct*, false>; _UnaryOperation = my_struct (*)(const input_struct&)]':
<source>:37:31:   required from here
gcc-4.8.5/include/c++/4.8.5/bits/stl_algo.h:4926:12: error: no match for 'operator=' (operand types are 'my_struct' and 'my_struct')
  *__result = __unary_op(*__first);
            ^
gcc-4.8.5/include/c++/4.8.5/bits/stl_algo.h:4926:12: note: candidate is:
<source>:11:8: note: my_struct& my_struct::operator=(my_struct&)
 struct my_struct
        ^
<source>:11:8: note:   no known conversion for argument 1 from 'my_struct' to 'my_struct&'

Versioning

Compiler: GCC 4.8.5 Flags: -O3 -std=c++03 Boost: 1.75