tags:

views:

136

answers:

2

I am trying to understand some C++ syntax:

template<class T>
class Foo 
{
   Foo();

   template<class U>
   Foo(const Foo<U>& other);
};

template<class T>
Foo<T>::Foo() { /*normal init*/ }

template<class T>
template<class U>
Foo<T>::Foo(const Foo<U>& other) { /*odd copy constructed Foo*/ }

So, I wrote code like this, and it happens to compile fine in windows and linux. What I don't understand is why the copy constructor has two templates defined as so. Basically, I had to expirment a bit before I found the correct syntax and I would like to know why that particular syntax is correct, and not something like template<class T, class U>.

+7  A: 

The first template ( with parameter T) says that the class is templated with a parameter T.

The second template (with parameter U) says that the member function of the templated class (with parameter T) is templated with parameter U - that is the constructor.

In fact, here you have a template class that will generate as many copy constructor as types used as parameter of the constructor.

In the specific case of copy constructor, you shouldn't do this but instead :

template<class T>
class Foo 
{
   Foo();

   Foo(const Foo<T>& other);
};

template<class T>
Foo<T>::Foo() { /*normal init*/ }

template<class T>
Foo<T>::Foo(const Foo<T>& other) { /*odd copy constructed Foo*/ }

Because in you example it's not copy constructor but constructor that take type U as parameter: a convertion constructor... that is hard to predict.

Klaim
There can be legitimate reasons for wanting to have implicit construction of a Foo<T> from a Foo<U>, but you're right that it's not really a copy constructor then.
Tyler McHenry
Actually, in my actual code, I have both Foo<T> and Foo<U> type constructors, but the point is well taken. Im glad the compiler can work out this stuff..
Voltaire
Yes it's necessary because as T. McHenry said, in some cases you want this behavior. When you want to manage several types of number types for example, might be a good idea to make your member function templated to have only one definition for all the types, ignoring if it's a class or a basic type.
Klaim
imagine shared_ptr<T> which allows constructing from shared_ptr<U> as long as U* converts to T*
Johannes Schaub - litb
I would suggest editing: "...as many copy constructor as types..." and to remove "copy" from this line. As you point out later this is never a copy constructor - and even if he leaves this constructor in, he should also add the version you have.
Richard Corden
Most people call this a _converting_ constructor.
Greg Rogers
Right, modified.
Klaim
+4  A: 

It has to have separate template clauses for each template that is involved. Here, two templates are involved, that all deserve their (non-empty) template clauses:

  • The class template Foo
  • The constructor template

Consider this case which fails because of the ambiguity as to where the parameter U belongs to

template<typename T>
struct A {
    template<typename U> void f();
};

template<typename T, typename U>
void A<T>::f() { }

Now, what is up with the parameter U? Sure the compiler could guess it could belong to f, but guesswork is not what the compiler likes :) The existing rule says that depending on the nesting of templates, template clauses appear in the right order. Everything is clear then.

Even if one comes up with a rule how to match the parameters to arguments of the templates involved (so far i don't see a real difficulty in doing that), it would be inconsistent. Because as of now, one template clause lists all parameters that the corresponding template accepts. Much like a function parameter list. If we would put everything into one clause, that clear semantic could be broken - not to mention that when we put the definition into the class again, all of a sudden the template would get its own clause:

// provides arguments for A's parameters, then for f ones 
// when it's called
A<int> a; 
a.f<bool>();

It's much more natural when we have separate template clauses that catch each their own arguments. So, the syntax for the above wrong definition is

template<typename T> 
 template<typename U>
void A<T>::f() { }

Now, also the reader of the code immediately sees that this is a definition of a member template, and not a (potential accidentally declared but unused) second parameter for A.

Johannes Schaub - litb