Search code examples
c++functiontemplatesmatrixvariadic

C++ non recursive function with variable number of args


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


Solution

  • 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;
    }