3

I'm looking for advice as to how to proceed with this class hierarchy I'm building in C++.

Base class is Matrix:

class Matrix
{
protected:

    int rows;
    int columns;
    double* values;

public:

\\lots of stuff goes here. bla di bla di bla.

    virtual Matrix operator+(const Matrix& addend) const;

\\etc.
}

Squarematrix is inherited from Matrix

class Squarematrix : public Matrix
{
public:

    Squarematrix operator+(const Squarematrix& addend) const;

}

Operator+ returns a matrix or a squarematrix respectively. Since operator+ is a virtual function this wont compile, as it must have the same return type in all classes.

So what are my options?

I could use an ordinary function instead of virtual. This is a bit annoying, but wouldn't cause a problem under most circumstances.

I could return a matrix in all cases. This would basically make my squarematrix class a right pain in the *** to use, as I would have to constantly downcast from matrix to squarematrix.

I could return a reference to a squarematrix. Then the matrix would have to be stored on the heap and there's no way to make sure its deleted safely. Especially if I do something like this:

squarematrix a=b+(c+d);

(c+d) will be stored on the heap and have no pointer to it so will be leaked.

Is there any way to keep virtual functions and still have different return types?

What would you advise in this situation?

Thanks for your help. Looking forward to hearing from you.

Yair Halberstadt
  • 5,733
  • 28
  • 60
  • No. An overridden virtual function must return the same type. End of story. In this, very common, design pattern, the virtual functions return smart pointers to the base class. This works out fairly well when the classes are designed correctly. – Sam Varshavchik Feb 22 '17 at 21:42
  • 1
    @SamVarshavchik Not always. It is common to have covariant return types, for example in `clone()` member functions. – juanchopanza Feb 22 '17 at 21:43
  • 4
    Look at [Circle-ellipse_problem](https://en.wikipedia.org/wiki/Circle-ellipse_problem). – Jarod42 Feb 22 '17 at 21:44
  • 1
    Why does `operator+` need to be virtual? – Kerrek SB Feb 22 '17 at 21:48
  • @Jarod42. Useful link. That problem has been bothering me all the way through building this class. Now I know what its called. I can't see the direct relevance to this question though. – Yair Halberstadt Feb 22 '17 at 22:11
  • @Kerrek SB. It doesn't, at least any more than any other class which uses virtual functions. But it would be useful to know if there's a general solution to this kind of problem. – Yair Halberstadt Feb 22 '17 at 22:13
  • @SamVarshavchik. I understand that's the case. I was really asking for advice as to how to get around the limitations of virtual functions. Do you have a link to somewhere discussing these smart pointers? Thanks. – Yair Halberstadt Feb 22 '17 at 22:18
  • Yeah, I've got a link. They are described in [every modern C++ book](http://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). – Sam Varshavchik Feb 22 '17 at 22:21
  • For a simple class like this, virtual functions, smart pointers and all that stuff are totally overkill imo. This add unnecessary complexity to the program and there is runtime overhead for virtual functions, dynamic memory allocation and indirection through pointers. I'd go with the answer of R Sahu. – zett42 Feb 22 '17 at 22:49

2 Answers2

6

I would recommend:

  1. Remove Squarematrix.
  2. Add a constructor to Matrix to construct a square matrix.
  3. If the knowledge of whether a matrix is square matrix is helpful for your application, add a member function in Matrix to answer that query.

class Matrix
{
   public:

      Matrix(int r);        // Construct a square matrix.
      Matrix(int r, int c); // Construct a rectangular matrix.

      bool isSquareMatrix() const { return (rows == columns); }

      Matrix operator+(const Matrix& addend) const;

   private:

      int rows;
      int columns;
      double* values;

}
R Sahu
  • 204,454
  • 14
  • 159
  • 270
  • Doesn't that lessen type safety, since any functions which only apply to squarematrices (inverse, diagonalise, determinant etc) have to be checked at runtime rather than compile time to see if they are valid? – Yair Halberstadt Feb 22 '17 at 22:22
  • @YairHalberstadt, yes, it does. However, consider the case where you have `Submatrix` as a class. In order to be compute the determinant of `Matrix*` or `Matrix&`, you'll have to check whether they underlying object is `Submatrix`. That will require a `down_cast`. That is not any better. You could provide a provide a `virtual` function to compute the determinant. However, you'll have to do something about that for a rectangular matrix. There is no getting around the fact that certain operations are allowed only for a subset of objects and you have to deal with the differences. – R Sahu Feb 22 '17 at 22:31
1

This is known as return type covariance (https://en.wikipedia.org/wiki/Covariant_return_type).

It was not supported by old compilers, but is supported by many now. For example my code compiles fine in Visual Studio 2017. Here is an article on its use and limitations in c++: https://aycchen.wordpress.com/2009/08/17/covariant-return-type-in-cpp/.

It is not supported in C# yet, but is being considered for a future version. See https://github.com/dotnet/csharplang/issues/49.

It is also supported by newer versions of Java. See https://blogs.oracle.com/sundararajan/covariant-return-types-in-java.

Other than implementation issues, as far as I know there is no reason for it not to be added to a polymorphic language. I don't believe it can cause errors, although due to an imperfect implementation in Java it can cause bugs-see https://dzone.com/articles/covariant-return-type-abyssal.

Yair Halberstadt
  • 5,733
  • 28
  • 60