views:

129

answers:

3

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.

+3  A: 

Might want to point out that following declaration is NOT copy constructor.

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

a copy constructor for derived<base, another_base> should take const derived<base, another_base>& as input, not arbitrary const derived<X,Y>&


A copy constructor has as its first parameter a (possibly const or volatile) reference to its own class type. It can have more arguments, but the rest must have default values associated with them.

http://en.wikipedia.org/wiki/Copy_constructor

YeenFei
Correct. That constructor is meant to be built out of a type X that cannot be another derived instantiation. The copy constructor for derived is implicitly defined by the compiler.
Noah Roberts
+2  A: 

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]

SCFrench
Thanks. I quoted that section to another answer but I guess they decided to delete it. Must have been another rating point whore.
Noah Roberts
+5  A: 

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.

Johannes Schaub - litb
This is all probably way above my head but would I be right in saying that if templates are not used as copy constructors then the `derived` struct in the question is just using a synthesised copy constructor?
Troubadour
@Troubadour yes if template instantiations are not used for copy constructors, then it will always use the synthesized copy constructor in the base. There is much to this matter... i recently had a mail exchange with someone else and a lot of topics came up about this.. it's messy :)
Johannes Schaub - litb
@litb: Okay, but the point I'm driving at is that in the simplified example `derived` _itself_ should be using a synthesised copy constructor? If so then the example works trivially and the oddness is then how @Noah got it to break by defining a copy constructor for `another_base`. It is way past my bedtime so I may be rambling incoherently...
Troubadour
Almost accepted this as answer. That's definitely the behavior that I'm getting but the conditions you mention don't seem to apply. This IS visual studio, but it's version 2010. New standard is not finished but they appear to at least have tried to apply it since your code compiles. It also seems to be inherently linked to multiple-inheritance as not until a copy constructor is declared in the other base class does the problem appear. Good answer though. Might be more helpful to provide section/chapter so readers (including me) can look it up for themselves.
Noah Roberts