-1

I have a Matrix template class and I need a function to set it's elements with variable number of args.

I should be able to call it like this:

aghMatrix<string> matrix;
matrix.setItems(2, 3, "torzmiae", "jestdnaci", "tablickore", "wyrazobed", "oelmntai", "rozmiaecy");

Where first integer is rows number, second is columns and rest (R * C) arguments are elements that I should put into matrix.

It should work with any data types, not only primitive ones.

For now, my function looks like this:

template<typename T>
template<typename... ARGS>
void aghMatrix<T>::setItems(const int rows, const int cols, ARGS... args) {
    array<T, sizeof...(args)>unpacked_args {args...};

    int row = 0;
    int col = 0;
    for (T arg : unpacked_args)
    {
        this->matrixPtr[row][col] = arg;
        col++;
        if (col == this->cols) {
            row++;
            col = 0;
        }
    }

    return;
}

I assumed my matrix object is able to hold all elements. It does compile with many warnings about casting everything to unsigned int, but the program doesn't work anyway (it freezes on start).

Class declaration:

template<typename T>
class aghMatrix {
public:
    [...]

    template<typename... ARGS> void setItems(const int rows, const int cols, ARGS... args);

    [...]

private:
    T **matrixPtr;
    int rows;
    int cols;

    void createMatrix(const int row, const int col);

    bool checkRowCol(const int row, const int col) const;
};

Github project

Łukasz Szcześniak
  • 1,417
  • 11
  • 23
  • `array` is an array that takes only `size_t` elements. You're trying to put `"torzmiae"` in there? Not gonna work. – DeiDei May 01 '16 at 20:18
  • I do not say my solution is good. It's just something I found here on SO. If you have a better way of solving my problem I will gladly try to use it. – Łukasz Szcześniak May 01 '16 at 20:22
  • what is the type of `matrixPtr`? and why would you put strings in `size_t`s? – Guillaume Racicot May 01 '16 at 20:24
  • matrixPtr is type of T**. Sorry, I just were trying numerous solutions and its last one that at least compiled. – Łukasz Szcześniak May 01 '16 at 20:26
  • In that case `array` would be a good start. Also get rid of the `size_t` in the `ranged for` - make it `T` or `auto`. – DeiDei May 01 '16 at 20:29
  • 1
    Please show us a more complete example. We don't see how `matrixPtr` is initialized, so we can't debug. Take a visit to http://stackoverflow.com/help/mcve – Guillaume Racicot May 01 '16 at 20:30
  • I made improvements suggested by DeiDei, but it didnt help much. I provided more info also with link to full project at github. – Łukasz Szcześniak May 01 '16 at 20:49
  • 1
    If `matrixPtr` is of type `std::string**` what are all those pointers pointing to? You dereference uninitialized pointers, that's UB. – StoryTeller - Unslander Monica May 01 '16 at 20:57
  • 3
    What about std::initializer_list ? To me, variadic templates are useful when the types are heterogeneous, but in your case, they all have the same type, so I think an array is more appropriate. – Johan Boulé May 01 '16 at 20:59
  • Johan Boule, doesnt it require function to be called like setItems(2, 3, {"string1", "string2"}) etc? If yes - that's sadly not solution for me. – Łukasz Szcześniak May 01 '16 at 21:02
  • Github link was not what people asked for. Please read and follow this [MCVE] -- this may require work on your part. – Yakk - Adam Nevraumont May 01 '16 at 21:05
  • I think it is clear what the OP wants. True, the question could be cleaned up, but the semantics of the code doesn't matter. He seems to only want to be able to load a variable number of elements into a matrix. –  May 01 '16 at 23:47
  • I provided some more code except github link. I just wanted to keep post clean, and provide a link if someone needs more information... sorry. – Łukasz Szcześniak May 01 '16 at 23:54
  • I agree with @JohanBoule. Much better design. –  May 02 '16 at 00:08
  • Yeah, I also agree with statement, that std::initializer_list would be great way of solving this problem. But my solution needs to cope with testing functions, which are non-changeable for me - and they require syntax provided in question. – Łukasz Szcześniak May 02 '16 at 00:51

1 Answers1

1

EDIT: Oops! I just noticed you said "non recursive," so I presume the following pattern doesn't work for you. I'll still leave it hanging here for now, but I have provided also a non recursive solution below (which is based on va_list and hence only works with POD types)

If I understand correctly what you want to do, then you probably want the recursive variadic argument unpacking pattern; something like this seems to do the trick...

#include <iostream>

using namespace std;

// Helper for build_matrix, taking zero variadic arguments.
// This serves as the termination in the recursive unpacking of the args.
template<typename T>
void build_matrix_helper(T**, size_t, size_t, size_t, size_t) { return; }

// Helper for build_matrix, taking at least one variadic argument.
template <typename T, typename ...ARGS>
void build_matrix_helper(T** matrix, size_t curr_row, size_t curr_col, 
                         size_t row, size_t col, const T& first, ARGS...rest) {
  if (curr_col < col) {
    matrix[curr_row][curr_col] = first;
    ++curr_col;
    return build_matrix_helper<T>(matrix, curr_row, curr_col, row, col, rest...);
  }
  else {
    ++curr_row; 
    curr_col = 0; 
    return build_matrix_helper<T>(matrix, curr_row, curr_col, row, col, first, rest...);
  }
  return;
}

// Bare bones implementation.
template<typename T, typename ...ARGS>
T **build_matrix(size_t row, size_t col, ARGS...elements) {
  T **new_mat = new T*[row];
  for (size_t j = 0; j < row; ++j)
    new_mat[j] = new T[col];

  build_matrix_helper<T>(new_mat, 0, 0, row, col, elements...);
  return new_mat;
}

int main() {
  int **nm = build_matrix<int>(2, 3, 1, 2, 3, 4, 5, 6);

  for (size_t i = 0; i < 2; ++i) {
    cout << "[" << i + 1 << "]: ";
    for (size_t j = 0; j < 3; ++j)
      cout << nm[i][j] << " ";
    cout << endl;
  }

  delete[] nm;
  return 0;
}

In general, you want to avoid any direct manipulation of memory as much as possible. Also avoid as much as possible any casting voodoo unless you absolutely need it (which also ties in with direct memory manipulation).

Anyway, can use a non recursive solution below, using std::va_list.

NOTE Since this uses va_list, it does not work with non POD types.

#include <iostream>
#include <cstdarg>

using namespace std;

template <typename T>
T **build_matrix(size_t row, size_t col, ...) {
  va_list args;
  T **matrix = new T*[row];

  va_start(args, col);

  for (size_t i = 0; i < row; ++i) {
    matrix[i] = new T[col];

    for (size_t j = 0; j < col; ++j)
      matrix[i][j] = va_arg(args, T);
  }

  va_end(args);

  return matrix;
}

int main() {
  int **nm = build_matrix<int>(2, 3, 1, 2, 3, 4, 5, 6);

  for (size_t i = 0; i < 2; ++i) {
    cout << "[" << i + 1 << "]: ";
    for (size_t j = 0; j < 3; ++j)
      cout << nm[i][j] << " ";
    cout << endl;
  }

  delete[] nm;
  return 0;
}

EDIT Initializer lists

As has been suggested in the comments to your OP, it is better to use initializer lists. I know this isn't what you asked for originally, but maybe it's worth considering:

#include <iostream>
#include <stdexcept>
using namespace std;

template <typename T>
T **build_matrix(size_t row, size_t col, initializer_list<T> il) {
  if (il.size() != row*col)
    throw out_of_range("Number of elements does not match matrix dimensions!");

  size_t curr_row = 0;
  size_t curr_col = 0;
  T **nm = new T*[row];
  nm[0] = new T[col];

  for (T elm : il) {
    if (curr_col == col) {
      ++curr_row;
      nm[curr_row] = new T[col];
      curr_col = 0;
    }
    nm[curr_row][curr_col] = elm;
    ++curr_col;
  }

  return nm;
}

int main() {
  int **nm = build_matrix<int>(2, 3, {1, 2, 3, 4, 5, 6});

  for (size_t i = 0; i < 2; ++i) {
    cout << "[" << i + 1 << "]: ";
    for (size_t j = 0; j < 3; ++j)
      cout << nm[i][j] << " ";
    cout << endl;
  }

  delete[] nm;
  return 0;
}
  • Thanks very much for your effort. Actually it might be my bad to not take recursive solution into account. I just would like to avoid it - but if it's the only way I will take it. I am afraid va_list won't work for non-primitive structures. – Łukasz Szcześniak May 01 '16 at 23:41
  • No, it's not the only way. See the added non recursive solution. –  May 01 '16 at 23:42
  • Yeah, but cstdarg doesn't let me pass for example std::string as a parameter :/ – Łukasz Szcześniak May 01 '16 at 23:50
  • By the way, why the non recursive requirement? –  May 02 '16 at 00:04
  • I just thought non-recursive way would be easier for me to understand/implement. Although I see now that recursive solution also isn't that hard. I will give it a go tomorrow and check if that solves my prob. – Łukasz Szcześniak May 02 '16 at 00:47
  • I guess tail-recursion optimisation doesn't make sense with variadic templates because since there's one less argument between the caller and the callee, it's actually not the same template-instanciation that's being called, i.e. it's not a true recursion! – Johan Boulé May 02 '16 at 06:13
  • @JohanBoule: Yes, you're right that this isn't true recursion (and no, I don't think tail call optimization applies). At compile time, the compiler generates all the called functions. –  May 04 '16 at 22:10
  • @William : I was considering this, because if you pass a lot of arguments, I'd be worried it generates "inflated" code. The initializer loop doesn't have this problem. @.Viters : This could be a valid point to ask for the interface to change. Another point : there probably is a lower compiler limit on the number of template arguments than on the number of items in an initializer list, and the compilation speed might make a difference too. – Johan Boulé May 06 '16 at 16:01