Unify the express of a complex number in initializer list and istream field

Viewed 48

As an old c99 person, I was often stubled upon the curly brakets initialization. In the `initializer_list`, I have to use {r, i} for a complex number. On the other hand, I have to use (r, i) for `complex` in the istream field. Here, I cut a part of my class that is able to run and give examples under codeblock 20.03 with MinGW 8.1.0.

#ifndef __tMatrix_class__
#define __tMatrix_class__
#include <iostream>
#include <initializer_list>
#include <iomanip>
#include <complex>
#include <sstream>
template <typename T> class tMatrix
{
  public:
   T *ptr;
   int col, row, size;
   inline T* begin() const {return ptr;}
   inline T* end() const {return this->ptr + this->size;}
   inline T operator()(const int i, const int j) const { return ptr[i*col+j]; } // r-value
   inline T&operator()(const int i, const int j) { return ptr[i*col+j]; } //l-value
   inline tMatrix(): col{0}, row{0}, size{0}, ptr{0} {;}
   tMatrix(const int i, const int j): col(j), row(i), size(i*j) {
       ptr = new T [this->size] ; }
   tMatrix(const std::initializer_list< std::initializer_list<T> > s):tMatrix<T>(s.size(), s.begin()->size())
     {
       int j = 0;
       for (const auto& i : s) {  std::copy (i.begin(), i.end(), ptr + j*col); ++j ; }
     }
   tMatrix(const tMatrix<T>&a) : tMatrix<T>(a.row, a.col)
    {
        std::copy(a.begin(), a.end(), this->ptr);
    }
   tMatrix<T>& operator=(tMatrix<T>&&a)
    {
       this->col = a.col;
       this->row = a.row;
       delete [] this->ptr;
       this->ptr = a.ptr;
       a.ptr = nullptr;
       return *this;
    }
   tMatrix<T>& operator=(const tMatrix<T>&a)
   {
      if (col==a.cpl && row==a.row) std::copy(a.begin(), a.end(), this->ptr);
      else { tMatrix<T>&&v(a); *this = std::move(v);}
      return *this;
   }
   tMatrix<T>& operator=(const std::initializer_list<std::initializer_list<T> > a)
   {
      tMatrix<T> &&v = a;
      *this = std::move(v);
      return *this;
   }
  ~tMatrix() {delete [] this->ptr;}
   void operator<<(const char*s)
    {
       std::stringstream ss;
       ss.str(s);
       for (int i=0; i<this->size; i++){
            if (ss.good()) ss >> this->ptr[i];
            else return;
         }
    }
}; //end of class tMatrix
template <typename X> std::ostream& operator<<(std::ostream&p, const tMatrix<X>&a)
{
   p << std::fixed;
   for (int i=0; i<a.row; i++) {
       for (int j=0; j <a.col; j++) p << std::setw(12) << a(i, j);
       p << std::endl;
   }
   return p;
}
using CMPLX = std::complex<double>;
using iMatrix = tMatrix<int>;
using rMatrix = tMatrix<double>;
using cMatrix = tMatrix< CMPLX >;
#endif
int main()
{
 cMatrix cx(2,2);
 cx = { { {1,2},{3,4} }, { {5,6}, {7,8} } };
 std::cout << cx << std::endl;
 cx << "(1,2) (3,4)";
 std::cout << cx << std::endl;
 return 0;
}

The above code renders correct format of complex number, and prints

$ ./ttt_mtx_init_fin_tmp.exe
(1.000000,2.000000)(3.000000,4.000000)
(5.000000,6.000000)(7.000000,8.000000)

(1.000000,2.000000)(3.000000,4.000000)
(5.000000,6.000000)(7.000000,8.000000)

But if I use the `()` in the initializer_list and `{}` in the istream filed, the results are all wrong. If I chagned the relavant part of main() to :

cx = { { (1,2),(3,4) }, { (5,6), (7,8) } };
std::cout << cx << std::endl;
cx << "{1,2} {3,4}";
std::cout << cx << std::endl;

Which renders all wrong values (compared with above):

$ ./ttt_mtx_init_fin_tmp.exe
(2.000000,0.000000)(4.000000,0.000000)
(6.000000,0.000000)(8.000000,0.000000)

(2.000000,0.000000)(4.000000,0.000000)
(6.000000,0.000000)(8.000000,0.000000)

I found it is rather confusion. So, my questions: is there a way to make these two expressions a same form? Many thanks for any helps.

2 Answers

I do not know any way to make std::istream::operator>> use { and } for std::complex, but if you are fine with using a helper, then you can replace the () in the input with {} and forward the input to the original operator>>:

#include <iostream>
#include <complex>
#include <sstream>
#include <algorithm>

template <typename T>
struct complex_reader {
    std::complex<T>& target;
};

template <typename T>
complex_reader<typename T::value_type> get_complex_reader(T& t){ return {t};}

template <typename T>
std::istream& operator>>(std::istream& in,complex_reader<T> cr){
     std::string input;
     std::getline(in,input,'}');                        // read till `}`
     std::replace(input.begin(),input.end(),'{','(');
     input += ')';
     std::stringstream ss{input};
     ss >> cr.target;                                   // call the original >>
     return in;
}

int main()
{
    std::stringstream ss{"{2,2}"};
    std::complex<double> x;
    ss >> get_complex_reader(x);
    std::cout << x;
}

Output:

(2,2)

However, you would have to write a similar helper to get consistent output (you may not provide an operator<< for std::complex<T> directly). Also note that the above implementation is a little simplistic. It reads from the stream until it encounters a }. For invalid input this may result in undesired effects and more sophisticated input validation is required.

Note that the operator>> takes the complex_helper by value to allow passing temporaries. Thats fine, because the member is a (non-const) reference.

This is not an answer, but a reasoning of my choice. After a series of cross conversions with `largest_prime_is_463035818`, I figured out what is my best choice for now (many thanks to his time and patience). A bottom line is becoming clear to me that I will not alter the input format of istream that is too much changed for pratical purpose, since file input is the major method to fetch data for a large matrix.

Under this constrain, I try to make the appearance of initializer_list as friendly as possible. I did some experiments, and found that the complex_literals expression is acceptable by initializer_list. And it looks ok to me.

using namespace std::complex_literals;
int main()
{
  cMatrix cx(3,2);
  cx =  { { 1+2.2j , 4j}, { 5.3+6.5j , 8.3j}, {8.3, 5.6+4j} };
  std::cout << cx << std::endl;
  cx <<  " (1,2) (3,4) (5,6) (7,8) (2.3, 3.4) (2,7.8) ";
  std::cout << cx << std::endl;
  return 0;
}

And it works.

$ ./a.exe
  (1.000000,2.200000) (0.000000,4.000000)
  (5.300000,6.500000) (0.000000,8.300000)
  (8.300000,0.000000) (5.600000,4.000000)

  (1.000000,2.000000) (3.000000,4.000000)
  (5.000000,6.000000) (7.000000,8.000000)
  (2.300000,3.400000) (2.000000,7.800000)

Thank you for your patience, and please let me know if there are better ways.

Related