Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

rationale behind c++ implicit copy and move constructor?

My understanding about c++ implicit copy constructor is similar to

T(T const& x) : 
    base1(x), base2(x) ... , 
    var1(x.var1), var2(x.var2)...
{}

Move constructor, copy & move assignment also follows similar pattern.

Why was it not defined similar to the following?

T(T const& x) : 
    base1(static_cast<base1 const&>(x)),
    base2(static_cast<base2 const&>(x)) ... , 
    var1(x.var1), var2(x.var2)...
{}

Example

I had a class which had implicit copy/move constructor/assignment operator, as well as some converting constructors. I was delegating the job to some implementation class.

class common_work //common implementation of many work like classes
{
   common_work(common_work const&) = default;
   common_work(common_work&&) = default;// ... implicit constructors work for me.
   //a forwarding constructor which can take many work like objects
   template<class T, enable_if<work_like<T> > >
   common_work(T&& x) { ... }
};
class work1 //one of the implementation
{
   work1(work1 const& ) = default;
   work1(work1&& ) = default; ...
   common_work impl_;
};

This was fine, as work1 copy/move constructors was calling copy/move constructor for common_work, and forwarding constructor was used by other constructors [not shown in code] which converts from another kind of work.

Then I thought to inherit work1 from common_work for EBO and other reasons. So the new work1 class looked like

class work1 : private common_work
{
   work1(work1 const& ) = default;
   work1(work1&& ) = default; ...
};

But as, work1 is a work_like class, suddenly the forwarding constructor was getting a better match, as the copy/move constructor for common_work requires a static_cast from derived to base.

NOTE :

  • There is a similar kind of example given by Scott Meyers, where copy construction triggers forwarding constructor as copy constructor requires a const addition, while forwarding constructor requires none. But I think, that problem arises due to wrong class design, while problem here is due to argument passed to base class during implicit copy/move are not exact match.
  • I can not write a universal forwarding constructor/assignment, and delete the implicit ones, because deleted functions also participate in overload resolution and cause error if matched exactly.
  • The solution presently I have is to make common_work as a CRTP , i.e. derived class type passed as template argument, and in forwarding constructor filter it out as enable_if<and_<work_like<T>,not_<is_same<T,Derived> > > >. Otherwise I have to manually write copy/move constructor/assignment for work1 and static_cast to base classes explicitly, which is buggy, error prone, and maintenance hazard.
like image 515
abir Avatar asked Jul 18 '13 06:07

abir


1 Answers

This issue was discussed on the MSVC++ bugstracker page some years ago (so if it is not fixed yet, it is a known issue on MSVC++). The Standard says

  • ... the base or member is direct-initialized with the corresponding base or member of x.

I did test various compilers when I read the bugreport, and all of them "magically casted". The Standard appears to be quiet on the very details (also about the value category and c/v qualifiers), and just says "with the corresponding base ... of x", which IMO makes much more sense if you take it to mean "with not x, but with the corresponding base ... of x" rather than that you pass it the complete object (I would even go as far as saying that it can only make sense that way).

like image 179
Johannes Schaub - litb Avatar answered Jan 05 '23 20:01

Johannes Schaub - litb