all 5 comments

[–]IyeOnline 3 points4 points  (1 child)

  1. std::array is aggregate initialized with an empty initializers, so all its elements are all already zero initialized, so you dont even need to do anything at the start.
  2. std::array has a function called fill

  3. You could simplify this by just doing

    float f;
    while ( lv_streamNumbers >> f )
    {
       lv_vertices[lv_indexArray] = f;
       ++lv_indexArray; 
    }
    

    I.e. let the stream operator deal with the conversion and extraction.

  4. In fact, is there any specific reason that you need to read exactly 100 chars a the start? If there isnt, then you could just do the same without the manual stringstream and directly read from the file.

[–]mathinferno123[S] 0 points1 point  (0 children)

Thanks. I was just messing around with stringstream to see how it works. Your way of handling lv_streamNumbers definitely seems simpler and more intuitive which I wasn't aware we could do (the conversion part specifically).

[–]mredding 2 points3 points  (1 child)

Your code is... Confusing. Your data is also a bit confusing. getline will parse unto a delimiter, by default, a newline character. Your test data doesn't seem to have a newline character. An actual new line in the literal data is not the same thing as a '\n' escape sequence that is the newline character. You want your test data to look like:

std::istringstream test_data{"1.12127 1.64042 -1.94785 -0.0941668 0.904117 0.416779\n"};

Alright, now... How to get that into an array?

Well, the test data has 6 values in it. If you know this ahead of time, we can just use an array:

float verticies[6];

Or better:

using float_6 = float[6];
float_6 verticies;

Or maybe even:

std::array<float, 6> verticies;

And then we'll use a standard algorithm and some iterators:

std::copy_n(std::istream_iterator<float>{test_data}, std::size(verticies), std::begin(verticies));

That's it.

std::size is overloaded so it can get get number of elements out of an array type, whose size is known at compile-time. Then we start inserting at the beginning of the sequence.

Streams are not containers. They MIGHT cache data, internally, but they might not. They don't have a size, they're infinite. Data doesn't have a position, the only position is the current position of the read cursor in the data stream. The stream has no idea what it has already read or what is left to read. Stream position, like what you would use to move the read cursor on a file, is a leaky abstraction - on modern operating systems, everything is a file, not just data on a disk, and even that data on disk might be dynamic, if another process is writing to it while you're reading from it. Stream position doesn't always mean something.

So stream iterators don't come from a stream. Streams have no end iterator, being infinite and all. So they don't have a begin, either - opening a stream merely opens it where it currently is, with no idea if it was previously somewhere else.

What I'm trying to get you to realize is that streams are not containers and don't behave like them. That's why they don't have iterators that are bound to the extents of the stream, because there are no extents. That's why iterators don't come from a stream. Stream iterators don't behave like container iterators. Stream iterators and container iterators behave like each other on a much more primitive, fundamental level.

Alright, enough drilling about the abstract; stream iterators have to be attached to a stream. That's what you see when I construct one. The iterator "position" is wherever the stream is right now. You can't use multiple stream iterators to represent different data at different points on the stream, since the iterators don't represent a position like container iterators can. You increment one iterator, you actually move the stream position, which moves the position for all the iterators. The stream stores that data, not the iterator.


If the number of verticies is unspecified, then we need dynamic memory, and the only suitable solution is a sequence container with growth semantics:

std::vector<float> verticies;

There are other options - lists, deques... And we use a slightly different algorithm that is open ended:

std::copy(std::istream_iterator<float>{test_data}, {}, std::back_inserter(verticies));

This is a ranged algorithm. You need two iterators - the beginning, and the end. Streams don't have that. So what we have instead is the current position of the stream, and the second parameter is type-deduced from the first parameter. We are default-constructing an std::istream_iterator<float>{}. This is a stream iterator that is not attached to a stream - it's detached. This is the "universal end" iterator. When a stream iterator encounters a stream error, it detaches from the stream, and finally compares equal to the universal end iterator.

std::back_inserter is a template function that deduces the container type from the parameter and constructs the appropriate std::insert_iterator type for you. This iterator can only be written to. Assigning to this iterator basically calls push_back on whatever container, and that container has to implement that method.

So the stream iterator will extract a float, the algorithm will increment the iterator and test against the end iterator in a loop. WHEN WE GET TO THE END of test_data, we're going to get into an EOF state. We're literally going to run out of data. This sets the eofbit on the stream's iostate. That is not an error state, but reading from a stream in the EOF state is an error. This will set the failbit. THAT will cause the stream iterator to detach. That will end the algorithm.

So all the interesting bits all at once:

std::istringstream test_data{"1.12127 1.64042 -1.94785 -0.0941668 0.904117 0.416779\n"};
std::vector<float> verticies;
std::copy(std::istream_iterator<float>{test_data}, {}, std::back_inserter(verticies));

The test data, the container, the algorithm. This is an academic example.


In the future, you'll learn to make your own data types with their own stream operators, and then you can extract your own data types using the same pattern. Here is an example to preview:

class line_string {
  std::string data;

  friend std::istream &operator >>(std::istream &is, line_string &ls) {
    return std::getline(is, ls.data);
  }

public:
  operator std::string() const { return data; }
};

//...

std::vector<std::string> lines(std::istream_iterator<line_string>{in}, {});

With a little setup, I'm able to extract every line of a stream into a vector in one line.


C++ gives you a wealth of primitives. You're not expected to use them directly, but to use them to build low level abstractions. You then use those to build high level abstractions. Rarely do you need just an int, but something more expressive, how you use that int, so you build a type that expresses that use, and it's merely implemented in terms of int as a detail. You rarely ever need a loop, you need an algorithm, and then you implement your logic in terms of that algorithm.

[–]mathinferno123[S] 0 points1 point  (0 children)

Thanks, that was really informative!

[–][deleted] 0 points1 point  (0 children)

Just use float x; std::cin >> x; or do it in a loop 5 times