2

Suppose I have a Matrix class and I'd like to initialize my Matrix objects in two ways:

Matrix a = {1,2,3}  // for a row vector

and

Matrix b = {{1,2,3},{4,5,6},{7,8,9}} // for a matrix

As a result, I implemented two copy constructors as below

class Matrix {
private:
    size_t rows, cols;
    double* mat;
public:
    Matrix() {}
    Matrix(initializer_list<double> row_vector) { ... }
    Matrix(initializer_list< initializer_list<double> > matrix) { ... }
    ...
}

No matter how I change my interface, such as adding an explicit keyword or change the nested version to Matrix(initializer_list< vector<double> > matrix). It will always cause ambiguities between these two cases:

Matrix a = {1,2,3};n
Matrix b = {{1}, {2}, {3}};

I'm not quite familiar with the stuff like direct/copy initialization or implicit type conversion. Are there any solutions for this problem?

pedim
  • 69
  • 2
  • 5
  • Are you trying to adapt your matrix size based on the input? For example do you expect `{1, 2, 3}` to yield a 3x1 vector, and `{{1,2,3},{4,5,6},{7,8,9}}` to yield a 3x3 matrix? – Jonathan Mee Aug 05 '16 at 17:30
  • @JonathanMee absolutely. – pedim Aug 05 '16 at 21:08
  • @pedim So it's kinda surprising, if you use Visual Studio *it will* select the third constructor. I opened a question about it here: http://stackoverflow.com/q/38795374/2642059 – Jonathan Mee Aug 05 '16 at 21:11
  • @Byteventurer Aha, the guy who solved your problem has just posted in Jonathan Mee's question and gave a more rigorous explanation this time. – pedim Aug 05 '16 at 21:43

3 Answers3

4

There is no solution which will unambiguously work in every case. However, you can create ways to disambiguate cases:

template<typename T>
auto il(std::initializer_list<T> the_il) -> std::initializer_list<T> { return the_il; }

Matrix b = {il({1}), {2}, {3}};

However, I would personally suggest that you be explicit about it. If a user wants a matrix containing one row, then it should look like a matrix containing one row, not like a vector:

Matrix a = {{1,2,3}};

So I would suggest ditching the first overload altogether.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
2

Well, here's a very dirty trick:

#include <iostream>
#include <initializer_list>

struct Matrix
{
   template<class = void> Matrix(std::initializer_list<double>) { std::cout << "vector\n"; }
   Matrix(std::initializer_list<std::initializer_list<double>>) { std::cout << "matrix\n"; }
};

int main()
{
   Matrix a = {1, 2, 3};
   Matrix b = {{1}, {2}, {3}};
   (void)a; (void)b;
}

The two overloads cannot be distinguished based on conversions, so we rely on a subsequent step in the overload resolution process: a non-template function is preferred over a template specialization.

bogdan
  • 9,229
  • 2
  • 33
  • 48
  • 1
    _Disgraceful!_ I love it. I also had no idea that unnamed template arguments were allowed, seeing as I've never had to use this sort of thing... thankfully... _yet_. ;-) – underscore_d Aug 05 '16 at 21:48
0

Why not just create one constructor that takes a matrix, create a private function copy and inside copy you check for a row_vector.

private void Matrix::copy(const Matrix &matrix)
{
   if (matrix.rows == 1)
   {
      //row vector stuff here
   }
   else if (matrix.cols == 1)
   {
      //col vector stuff here
   }
   else
   {
      //matrix stuff here
   }
}
Richard
  • 54
  • 5
  • But my purpose is to receive two kinds of initializers. How can I use only one constructor to deal with two types of object?? – pedim Aug 05 '16 at 17:46
  • Why not initialize the row vector like this: Matrix a = {{1,2,3}} Removes the need for one of your constructors. – Richard Aug 05 '16 at 19:15
  • The OP wants to be able to receive two different types of argument for convenience, not do different things with a single type. This doesn't differentiate between argument types as requested, makes no effort to explain how this private function would be used/useful... and wouldn't even compile anyway: you either forgot a colon (and a newline), or you're thinking about Java/C#. – underscore_d Aug 05 '16 at 21:45