Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MI and implicit copy constructor bug (was: Under what conditions can a template be the copy constructor?)

I was pretty sure that the answer to that question was, "Never, ever can a template be the copy constructor."

Unfortunately, I just spent 3 hours figuring out why I was getting a warning about recursion, tracked it to the copy constructor, watched the debugger go insane and not let me look at the recursive code, and finally tracked it down to a missing '&' in a base constructor.

You see, I have this complex policy-based design host that's been working fine for a while now. I went about overriding two policies in one and ran into a recursive copy constructor. Narrowed it down to one policy that is required to provide a constructor that can take a type of XXX concept as its argument, but in this case I'm just discarding it. So I wrote

struct my_policy
{
  template < typename T >
  my_polity(T const) {} // missing '&'...oops
};

Now, my_policy is a base class to the host (of course) and this little typo caused recursion where the host's copy constructor passed itself up the chain to this, templated constructor rather than an implicit, compiler generated copy constructor. It would then of course call its copy constructor again to create the temporary.

The truly fascinating thing is that I can't recreate this in simplified code. Even with a sort of mock policy host example I can't make it happen. The following code does not exhibit the issue:

#include <boost/utility/enable_if.hpp>
#include <boost/mpl/bool.hpp>

struct base
{
  template < typename T >
  base(T const) {}
};

struct another_base 
{
  int x;

  another_base(int y) : x(y) {}
};

template < typename T >
struct is_derived : boost::mpl::false_ {};

template < typename T1, typename T2 >
struct derived : T1, T2
{
  template < typename T >
  derived(T const& x, typename boost::disable_if< is_derived<T> >::type * = 0) : T1(0), T2(x) {}
};

template < typename T1, typename T2 >
struct is_derived<derived<T1,T2>> : boost::mpl::true_ {};

int main() 
{
  derived<base, another_base> d(23);
  derived<base, another_base> x = d;
}

I am using boost's parameter library to make the 7 or so arguments to the host accessible by "name". Maybe that's the issue, I don't know. At any rate, I'm wondering if someone out there knows what specific conditions, if any, could cause a compiler to legitimately use the templated constructor for "base" as a copy constructor or from the implicit copy constructor for "derived".

Edit note:

I recreated the problem in the above code by giving "another_base" an explicit copy constructor:

struct another_base 
{
  int x;

  another_base(another_base const& b) : x(b.x) {}

  another_base(int y) : x(y) {}
};

Starting to conclude that this is a compiler bug unless someone can tell me why this is legitimate.

More information:

struct derived;

struct base
{
  base() {}

private:
  base(derived const&);
};

struct base2 
{
  base2() {}
  //base2 (base2 const&) {}
};

struct derived : base, base2 {};

int main()
{
  derived d1; derived d2(d1);
}

Looking more at Schaub's answer I took the above code and compiled it. It compiles just fine until you uncomment base2's copy constructor declaration. Then it will blow up in the way I'm assuming was expected with the original code (no access to private constructor in base). So templates aren't even part of the issue; you can recreate the problem without them. Looks like it's an MI issue, which VS has always been a little slow at getting right.

I've changed the tags to reflect this finding.

Posted to MS's bug repository

http://connect.microsoft.com/VisualStudio/feedback/details/587787/implicit-copy-constructor-calls-base-with-derived-type-under-specific-conditions

I included a work around in the example code.

like image 887
Edward Strange Avatar asked Aug 20 '10 00:08

Edward Strange


People also ask

What is implicit copy constructor?

An implicitly defined copy constructor will copy the bases and members of an object in the same order that a constructor would initialize the bases and members of the object.

What are the situations where a copy constructor is invoked?

In C++, a Copy Constructor may be called for the following cases: 1) When an object of the class is returned by value. 2) When an object of the class is passed (to a function) by value as an argument. 3) When an object is constructed based on another object of the same class.

What purpose does a copy constructor serve under what circumstances is it required?

The compiler provides a copy constructor if there is no copy constructor defined in the class. Bitwise copy will be made if the assignment operator is not overloaded. Copy Constructor is used when a new object is being created with the help of the already existing element.

What happens if a copy constructor is not defined?

Correctness/Semantics - if you don't provide a user-defined copy-constructor, programs using that type may fail to compile, or may work incorrectly.


2 Answers

With Visual C++, there is an issue with derived to base delegation of the copy constructor argument:

struct Derived;

struct Base {
  Base(){ }

private:
  Base(Derived const&);
};

struct Derived : Base { };

Derived d1;
Derived d2(d1);

This code is valid, but Visual C++ fails to compile it, because they call the base-class copy constructor using a Derived object. The Standard however require the compiler to pass a Base const (or Base in some cases) down to the base class.

This is the first part of your puzzle: The template is a better match, because the copy constructor would need a derived to base conversion, but your template accepts the derived class directly, and then would need another copy, and so on and so on. Notice that the template will not act as a copy constructor here (given the VC++ bug), just as the above declaration of Base(Derived const&) did not declare a copy constructor.

The second part is the answer to your other question: The Standard was ambiguous and not clear in C++03 as to whether instantiated templates could act as copy constructors or not. In a note, it says

Because a template constructor is never a copy constructor, the presence of such a template does not suppress the implicit declaration of a copy constructor. Template constructors participate in overload resolution with other constructors, including copy constructors, and a template constructor may be used to copy an object if it provides a better match than other constructors.

but a few paragraphs below that, it says

A member function template is never instantiated to perform the copy of a class object to an object of its class type.

Because of the context that this text appears on (forbidding by-value parameter copy constructors) one may argue that this is not forbiding an instantiation of a by-reference copy constructor from a template. But such arguing is moot, facing this vague wording.

The C++0x FCD clearified it, and removed the weird note. It now is clear that templates are never instantiated to perform a copy, no matter whether it would yield to by-reference or by-value parameters. But as explained above, if you happen to use VC++ by any chance, and it exhibits that behavior, then it has nothing to do with copy constructors.

like image 102
Johannes Schaub - litb Avatar answered Oct 23 '22 15:10

Johannes Schaub - litb


I'm pretty sure the answer to your question is "never".

Section 12.8.2 of the ANSI C++ Standard says

A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments.

Section 12.8.3 says

A declaration of a constructor for a class X is ill-formed if its first parameter is of type (optionally cv-qualified) X and either there are no other parameters or else all other parameters have default arguments. A member function template is never instantiated to perform the copy of a class object to an object of its class type. [Example:

struct S { 
    template <typename T> S(T);
};

S f();

void g() {
    S a( f() ); // does not instantiate member template
}

-- end example]

like image 25
SCFrench Avatar answered Oct 23 '22 13:10

SCFrench