Search code examples
c++classtemplatesvariadic-functionsellipsis

Unexpected type conversion when using C-style variadics to pass doubles


I tried a variable Args. The Matrix class works with int but when i use double i got wrong values with 23 when i dont convert it. I read that other types are automaticly converted

  • float arguments are converted to double as in floating-point promotion
  • bool, char, short, and unscoped enumerations are converted to int or wider integer types as in integer promotion" i don't get it why i get wrong values.
#include "matrix.h"
//#include "coordinate_sys.h"

int main(){

//Coordinate_3d<double>Coordinate_3(1,2,3);
//Coordinate_3.print_console();

Matrix<double>sick_matrix(2,2,4,20.5,(double)23,23,0);
sick_matrix.print_console();

while(1){}

return 0;
}
#ifndef MATRIX_H
#define MATRIX_H
#include <iostream>
#include <cstdarg>

template<typename T>
class Matrix{
private:
    T * arr_ptr;
    unsigned int rows;
    unsigned int cols;
    
public:
    Matrix(unsigned int rows, unsigned int cols, unsigned int args_ctr,...): rows(rows),cols(cols){
        //react to false input
        //--------------------------------------------------------------------------------------------
        if(cols * rows < args_ctr){ //to many args
            std::cout << "Matrix initilization with to many arguments" << std::endl;
            exit(EXIT_FAILURE);
        }
        //--------------------------------------------------------------------------------------------
        
        arr_ptr = new T[rows*cols];
        va_list args;
        unsigned int fill_var = 0;
        va_start(args,args_ctr);

        while (fill_var < args_ctr)
        {
            arr_ptr[fill_var] = va_arg(args,T);
            fill_var++;
        }
        
        va_end(args);   
    }

    void print_console(){for(unsigned int i = 0; i < rows*cols; i++){std::cout << arr_ptr[i] << std::endl;}}

};

#endif

Im going to do this with variable Template arguments but why is that error in the conversion?


Solution

  • The problem is that C-style variadic arguments give you absolutely no type safety. (double)23,23,0 passes a double and two ints, and you expect three doubles in the constructor.

    You should generally stay away from C-style variadics in C++.

    Solution A - std::initializer_list

    Matrix(unsigned int rows, unsigned int cols, std::initializer_list<T> args)
      : rows(rows), cols(cols) {
        // args.size() can be used to obtain the number of arguments
    }
    

    Solution B - statically sized matrix

    In almost all cases, you know the size of a matrix at compile time anyway. A lot of issues becomes trivial to solve if you turn the rows and columns into non-type template parameters:

    template <typename T, std::size_t Rows, std::size_t Columns = Rows>
    class Matrix {
      private:
        // ...
      public:
        T data[Rows * Columns];
    };
    

    You don't even need a constructor anymore, and could write:

    using Mat3f = Matrix<float, 3>; // convenience alias
    
    Mat3f mat{/* ... */}; // aggregate initialization, no constructor needed
    

    If you insisted on having a constructor, it could be a constructor taking just one parameter of type std::initializer_list<T>, and you could still use the syntax Mat3f{...}.