6

I have two cases of input for which I want to use the same method. The first case is that the given parameter is a std::string containing three digits that I need to convert into int:

std::string pointLine = "1 1 1";

The second case is that the given parameter is a std::string containing three "not yet doubles" that I need to convert into doubles:

std::string pointLine = "1.23 23.456 3.4567"

I have written the following method:

std::vector<double> getVertexIndices(std::string pointLine) {


vector<int> vertVec;

vertVec.push_back((int) pointLine.at(0));
vertVec.push_back((int) pointLine.at(2));
vertVec.push_back((int) pointLine.at(4));

return vertVec;

}

This works fine for the first case, but not for having a line that is supposed to be converted to doubles.

So I tried the solution Double split in C . I get that my delimiter would be " ".

This is what I came up with as for now, but the program crashes after first call of the following method:

std::vector<double> getVertexIndices(std::string pointLine) { 

vector<double> vertVec;
char * result = std::strtok(const_cast<char*>(pointLine.c_str()), " "); 

while(result != NULL ) {
    double vert = atof (result);
    vertVec.push_back(vert);
    char * result = std::strtok(NULL, " ");
}
return vertVec;

}

Community
  • 1
  • 1
Reatus
  • 289
  • 3
  • 11

3 Answers3

11

Instead of copying, you can directly initialize your vector from iterators.

// include <string>, <vector>, <iterator> and <sstream> headers
std::vector<double> getVertexIndices(std::string const& pointLine)
{
  std::istringstream iss(pointLine);

  return std::vector<double>{ 
    std::istream_iterator<double>(iss),
    std::istream_iterator<double>()
  };
}

This works exactly the same way for your ints. Your int-approach will not do what you intended for strings like "123 456 789"

user2436830
  • 439
  • 3
  • 5
  • Thanks! This works perfectly for what I want. Thanks for the remark regarding ints with more than one digit. I just ran into that problem. – Reatus Aug 26 '14 at 14:03
8

Use std::istringstream:

std::vector<double> getVertexIndices(std::string pointLine)
{
  vector<double> vertVec;
  std::istringstream s(pointLine);
  double d;
  while (s >> d) {
    vertVec.push_back(d);
  }
  return vertVec;
}

It's quite simple, really. You construct a stream which will read from the string. Then just use normal stream extraction to fill the vector.

Of course, you can leverage the standard library iterator adaptors and similar, to produce something like this:

std::vector<double> getVertexIndices(std::string pointLine)
{
  std::vector<double> vec;
  std::istringstream s(pointLine);
  std::copy(
    std::istream_iterator<double>(s)  // start
    , std::istream_iterator<double>()  // end
    , std::back_inserter(vec)  // destination
  );
  return vec;
}

As a side note (thanks to @ikh), you might want to change the function to take a const std::string & - there's no need to take the string by value.

Angew is no longer proud of SO
  • 167,307
  • 17
  • 350
  • 455
  • Thanks for the solution. I mostly like to initialize everything in advance. It helped, but I chose the other one, because I think that it is sleeker and having not to write a loop makes me think it is better regarding performance. Remark: That performance thing is just a feeling, not backed by knowledge. – Reatus Aug 26 '14 at 14:07
2

The main problem with your attempt is that strtok() will modify the pointer passed as input. Doing so with a pointer obtained via std::string::c_str() will wreak havoc inside the string object.

All other answers provided here were pretty good. From what you've stated, you only need to read 3 doubles (an x,y,z vertex), so you could optimize the function to use some other fixed-size container that doesn't allocate memory (std::vector will allocate). In the following example, I've used an std::tuple to return the results. I've also used std::stod() which is a C++11 function that parses a double from a string. It will throw an exception if conversion fails, which might also be useful for your application.

#include <iostream> // std::cout
#include <string>   // std::stod
#include <tuple>    // std::tuple

std::tuple<double, double, double> getVertexIndices(const std::string & pointLine)
{
    size_t xEnd, yEnd;
    const double x = std::stod(pointLine, &xEnd);
    const double y = std::stod(pointLine.substr(xEnd), &yEnd);
    const double z = std::stod(pointLine.substr(xEnd + yEnd));
    return std::make_tuple(x, y, z);
}

// Test case:
int main()
{
    const std::string pointLine1 = "1 2 3";
    const std::string pointLine2 = "1.23 23.456 3.4567";

    std::tuple<double, double, double> v1 = getVertexIndices(pointLine1);
    std::cout << "x = " << std::get<0>(v1) << std::endl;
    std::cout << "y = " << std::get<1>(v1) << std::endl;
    std::cout << "z = " << std::get<2>(v1) << std::endl;

    std::tuple<double, double, double> v2 = getVertexIndices(pointLine2);
    std::cout << "x = " << std::get<0>(v2) << std::endl;
    std::cout << "y = " << std::get<1>(v2) << std::endl;
    std::cout << "z = " << std::get<2>(v2) << std::endl;

    return (0);
}
glampert
  • 4,371
  • 2
  • 23
  • 49
  • Thank you for the explanations! I found another solution better to use, but your comments helped understand things more clearly! – Reatus Aug 26 '14 at 14:04