views:

466

answers:

2

As some of my code required implicit conversion between matrices of different types (e.g. Matrix<int> to Matrix<double>), I defined a templated copy constructor Matrix<T>::Matrix(Matrix<U> const&) instead of the standard Matrix<T>::Matrix(Matrix<T> const&):

template <typename T> class Matrix {
public:
    // ...
    template <typename U> Matrix(Matrix<U> const&);
    // ...
private
    unsigned int m_rows, m_cols;
    T *m_data;
    // ...
};

With an appropriate typecast added to the copy-constructor, this method flawlessly converted between matrices of different types. Surprisingly, it fails with a malloc error in the very situation where a simple copy-constructor would function: where U == T. Sure enough, overloading the copy-constructor with the default Matrix<T>::Matrix(Matrix<T> const&) signature solves the problem.

This is a poor solution, as it results in the wholesale duplication of the copy-constructor code (Literally an unchanged copy-and-paste). More importantly, I do not understand why there is a double-free malloc error without the duplicate code. Furthermore, why is the extremely verbose template <typename T> template <typename U> syntax required here as opposed to the standard, and much more succinct, template <typename T, typename U>?

Full source of the templated method, compiled using G++ v4.0.1 on Mac OS 10.5.

template <typename T> template <typename U> Matrix<T>::Matrix(Matrix<U> const& obj) {
    m_rows = obj.GetNumRows();
    m_cols = obj.GetNumCols();
    m_data = new T[m_rows * m_cols];

    for (unsigned int r = 0; r < m_rows; ++r) {
        for (unsigned int c = 0; c < m_cols; ++c) {
            m_data[m_rows * r + c] = static_cast<T>(obj(r, c));
        }
    }
}
+6  A: 

It fails because a template doesn't suppress the implicit declaration of a copy constructor. It will serve as a simple converting constructor, which can be used to copy an object when overload resolution selects it.

Now, you probably copied your matrix somewhere, which would use the implicitly defined copy constructor which does a flat copy. Then, the copied matrix and the copy would both in their destructor delete the same pointer.

Furthermore, why is the extremely verbose template <typename T> template <typename U> syntax required

Because there are two templates involved: The Matrix, which is a class template, and the converting constructor template. Each template deserves its own template clause with its own parameters.

You should get rid of the <T> in your first line, by the way. Such a thing does not appear when defining a template.

This is a poor solution, as it results in the wholesale duplication of the copy-constructor code

You can define a member function template, which will do the work, and delegate from both the converting constructor and the copy constructor. That way, the code is not duplicated.


Richard made a good point in the comments which made me amend my answer. If the candidate function generated from the template is a better match than the implicitly declared copy constructor, then the template "wins", and it will be called. Here are two common examples:

struct A {
  template<typename T>
  A(T&) { std::cout << "A(T&)"; }
  A() { }
};

int main() {
  A a;
  A b(a); // template wins:
          //   A<A>(A&)  -- specialization
          //   A(A const&); -- implicit copy constructor
          // (prefer less qualification)

  A const a1;
  A b1(a1); // implicit copy constructor wins: 
            //   A(A const&) -- specialization
            //   A(A const&) -- implicit copy constructor
            // (prefer non-template)
}

A copy constructor can have a non-const reference parameter too, if any of its members has

struct B { B(B&) { } B() { } };
struct A {
  template<typename T>
  A(T&) { std::cout << "A(T&)"; }
  A() { }
  B b;
};

int main() {
  A a;
  A b(a); // implicit copy constructor wins:
          //   A<A>(A&)  -- specialization
          //   A(A&); -- implicit copy constructor
          // (prefer non-template)

  A const a1;
  A b1(a1); // template wins: 
            //   A(A const&) -- specialization
            // (implicit copy constructor not viable)
}
Johannes Schaub - litb
Your explanation for the malloc error sounds spot on. Is there a specific reason for why compilers can't use a template member function as a copy-constructor? Thanks for the information and for the advice to avoid code duplication.
Mike Koval
If you have that template, it's not a function yet. Only usage of it with some template argument generates a (member-) function out of it (called a specialization). This is also the reason why member templates can't be virtual: You don't know in advance what functions are generated from it.
Johannes Schaub - litb
That makes a great deal of sense - this does not work for the very reason that one needs to either forward declare template parameters or include the full source of the implementation. Thanks again.
Mike Koval
Richard Corden
@Richard, good point there about the conversion sequence. I'll amend my answer.
Johannes Schaub - litb
+1  A: 

I'm not entirely clear from your question, but I suspect what is happening is that the default copy constructor (which does a memberwise copy only) is being used in some places in your code. Remember, not only the code you actually write uses the copy constructor - the compiler uses it too.

anon