4

Following case:

template <typename NumberType = double>
class A
{
   //constructur, destructor,...

   //member variable
   std::vector<std::vector<NumberType> matrix_;

   //member function
   void read(std::ifstream fileIn)
   {
       std::string line, word;
       unsigned int row = 0;
       while (std::getline(fileIn, line))
       {
         std::istringstream lineStream(line);
         unsigned int col = 0;
         while (std::getline(lineStream, word, ','))
            {
               matrix_[row][col] = std::stod(word); // how to convert to NumberType?
               col++;
            }
         row++;
      }
   }
};

I read matrix-like data from a csv file and want to store the entries in a container, whose type is a template type from a class. At the moment, I instantiate the class only for NumberType = double , so I hard-coded std::stod(word) for testing.

But how can I convert the string word to NumberType? NumberType can be float, double, long double,..., but not string, unsigned int, int,...

Aamir
  • 1,974
  • 1
  • 14
  • 18
Simon
  • 325
  • 1
  • 6
  • You could put `word` into a `stringstream` and use its `operator<<`. – wohlstad Aug 24 '23 at 06:32
  • In a `stringstream` or `istringstream` like I did it for `line`? – Simon Aug 24 '23 at 06:34
  • Why do you use `std::getline` again on `lineStream` (when it is already a line you got from the previous `std::getline`) ? – wohlstad Aug 24 '23 at 06:34
  • I was thinking of `stringstream`, but maybe `istringstream` can be used as well. – wohlstad Aug 24 '23 at 06:35
  • @wohlstad I found a similar approach using std::getline twice here: https://stackoverflow.com/questions/34218040/how-to-read-a-csv-file-data-into-an-array But if you have a simpler strategy to read the csv data, I would appreciate your input. – Simon Aug 24 '23 at 06:41
  • I see, I missed that `','` separartor in the 2nd `std::getline`. – wohlstad Aug 24 '23 at 06:43

3 Answers3

4

I would use explicit conversion functions for types you want to support. That way you will not leave conversion to chance (implicit conversions and/or probably incorrect casting).

#include <string>

template<typename NumberType>
NumberType ConvertTo(const std::string& from)
{
    static_assert(false, "no conversion for this type yet");
}

// provide specializations for supported conversions

template<>
double ConvertTo<double>(const std::string& from)
{
    return std::stod(from);
}

template<>
int ConvertTo<int>(const std::string& from)
{
    return std::stoi(from);
}


template <typename NumberType = double>
class A
{
    //constructur, destructor,...

    //member variable
    std::vector < std::vector<NumberType> matrix_;

    //member function
    void read(std::ifstream fileIn)
    {
        std::string line, word;
        unsigned int row = 0;
        while (std::getline(fileIn, line))
        {
            std::istringstream lineStream(line);
            unsigned int col = 0;
            while (std::getline(lineStream, word, ','))
            {
                // Use the template conversion function here :
                matrix_[row][col] = ConvertTo<NumberType>(word);
                col++;
            }
            row++;
        }
    }
};
Pepijn Kramer
  • 9,356
  • 2
  • 8
  • 19
3

You can create a std::istringstream from word and then use its operator>> to parse the numeric value (as it has an overload for the standard numeric types).

You can add the method GetNumber below to your class, and use it instead of std::stod.

#include <sstream>
#include <iostream>

template <typename NumberType = double>
class A
{
    // ...

public:
    static NumberType GetNumber(std::string const& word)
    {
        std::istringstream ss{ word };
        NumberType val{};
        ss >> val; // here the proper overload will be called according to the type NumberType 
        return val;
    }
};

int main() 
{
    std::cout << A<float>::GetNumber("123.123") << "\n";
    std::cout << A<double>::GetNumber("456.456") << "\n";
}

Output:

123.123
456.456

Note:
The GetNumber method above is not actually related to class A and could be placed anywhere.
I posted it as a part of class A to match to OP's code, and make it clear where it should be used.

wohlstad
  • 12,661
  • 10
  • 26
  • 39
  • This way you pay a hefty localisation cost on every conversion of a number ... if you know you are reading a number you should avoid stringstream. – Ahmed AEK Aug 24 '23 at 07:17
  • 1
    I agree, and if this is a bottleneck it should be optimizied. But the OP requested a generic way that will work for multiple types, and this is a way to do it. – wohlstad Aug 24 '23 at 07:18
  • Any reason to wrap this in a class? – Caleth Aug 24 '23 at 08:07
  • @Caleth I wrapped it in a class just because it matches the OP's posted code, with the same class name and template parameter name (so that it's clear where it should go and be used). No real reason besides that. I added a note to the answer. – wohlstad Aug 24 '23 at 08:16
0

Since you are only expecting float, double, long double, Just use std::stold, and let the implicit conversion do the narrowing cast to float or double, the cost of the narrow cast is not measureable, and you don't lose precision.

If you are also expecting an int then you should use the other answers.

Ahmed AEK
  • 8,584
  • 2
  • 7
  • 23
  • 1
    Narrowing casts should be avoided. There is a reason the {} initializers do not allow it. It all leaves conversion to "implicit" behavior which in large projects always will bite you in the end. – Pepijn Kramer Aug 24 '23 at 08:23