Pairs and stringstream - c++

In a previous question I have asked how I could read and comfortably transform a vector of strings into a vector of integers or doubles.
Now I am expanding that vector of simple types to vector of pairs of types (consider int, double, or even std::string, all stream-enabled types) separated by colons.
Example:
time:5, length:10
Should be read as a std::pair<std::string, unsigned int>, with something like this:
// Here declare what to parse: a string and an uint, separated by a colon
get_vector<std::pair<std::string, unsigned int>>(s);
How can I write a std::stringstream operator that does the trick?
I cannot figure how (or even if) I could play with getline (my code follows). I'd really like to leave the get_vector function as it is, if possible.
Thanks!
template <class F, class S>
std::stringstream& operator >> (std::stringstream& in, std::pair<F, S> &out)
{
in >> out.first >> out.second;
return in;
}
template <class T>
auto get_vector(std::string s) -> std::vector<T>
{
std::vector<T> v;
// Vector of strings
auto tmp = split(s);
// Magic
std::transform(tmp.begin(), tmp.end(), std::back_inserter(v),
[](const std::string& elem) -> T
{
std::stringstream ss(elem);
T value;
ss >> value;
return value;
});
return v;
}

Your operator>> receives a std::stringstream which in that moment contains a string like "time:5". The operator needs to parse that string by splitting it up into two tokens, then convert the first token to an F and the second to an S.
One way to do this is to use std::getline with a custom delimiter. You will end up with two std::string tokens. These can be converted, for example, with two internal std::istringstreams.
Here is some code you can try:
template <class F, class S>
std::stringstream& operator >> (std::stringstream& in, std::pair<F, S> &out)
{
std::string first_token;
std::string second_token;
std::getline(in, first_token, ':');
std::getline(in, second_token);
std::istringstream first_converter(first_token);
std::istringstream second_converter(second_token);
first_converter >> out.first;
second_converter >> out.second;
return in;
}
You may want to add some error handling for the case when there is no ':' in the string.
Note that you should not use std::stringstream if you do not want to combine input and output. Most of the times, you will want either std::istringstream or std::ostringstream.

Because I am new in English I am not sure to understand what you want.
I jest seeing that #Christian Hackl do what. So if this answer does not
relate to what you want please tell me and I will delete it.
I assume you need to extract a string from string or stream and then put them into a vector that contains a std::pair< std::string, std::size_t >
Say you have this string that can be an input-stream, too:
std::string string( "one:1, two:20, three:300, four:40000, five:500000" );
There is more than one way to do it, but I say it in this way!
So first you need to split this string and then put what has split, in to vector of pair.
std::string string( "one:1, two:20, three:300, four:40000, five:500000" );
std::stringstream io_stream;
std::basic_regex< char > regex( ", " );
std::regex_token_iterator< std::string::const_iterator > last;
std::regex_token_iterator< std::string::const_iterator > first( string.begin(), string.end(), regex, -1 );
std::vector< std::pair< std::string, std::size_t > > vector_of_pair;
std::string str_pair( "" );
std::size_t ui_pair( 0 );
while( first != last ){
io_stream.clear();
io_stream.seekg( 0 );
io_stream.seekp( 0 );
io_stream << *first << '\n';
std::getline( io_stream, str_pair, ':' );
// error:
// no match function to call: stream and int
// std::getline( io_stream, ui_pair);
io_stream >> ui_pair;
vector_of_pair.emplace_back( str_pair, ui_pair );
++first;
}
test
for( std::size_t index = 0; index < vector_of_pair.size(); ++index ){
std::cout << vector_of_pair[ index ].first << " and " << vector_of_pair[ index ].second << '\n';
}
output
one and 1
two and 20
three and 300
four and 40000
five and 500000
how it work
There is no difficult code to understand. It just by std::regex_token_iterator splits the string, then in a while loop send the value of it to is_stream, then read the line and send it to str_pair, but here because std::getline has not function to extracts int type, I used >> of stringstream itself, then emplace_back to vector and that's it.
NOTE
For:
How can I write a std::stringstream operator that does the trick?
I think now you can do it by yourself. Since I was not sure, I have not written the code with operator>>.

Related

Cpp: parse string snippets to tuple

I want to parse string snippets to a tuple:
example string: "Dolly Davenell,8809903417,1 Toban Circle,Luozhou"
tuple<string, unsigned int, string, string>
i read the strings from a file and store them with getline in a vector (myPersVec), where each vector element is a string as dscribed above.
Now my problem is that i don't know how to seperate each string element and fill it into each tuple element.
I know i can seperate the string elements with a delimeter character but how do i parse it into the tuple?
I then want to save each tuple into another Vector (my2ndVec)
My question is: Once i have the string tokens, how can i parse them to ONE tuple in the correct order?
auto makeT([](std::string line, std::vector<std::tuple<std::string, unsigned int, std::string, std::string>>& my2ndVec, std::vector<std::string> myPersVec) {
std::string token;
std::string deli = ",";
int pos = 0;
while ((pos = line.find(deli)) != std::string::npos) {
token = line.substr(0, pos);
std::cout << token << std::endl;
line.erase(0, pos + deli.length());
}
//how can i parse the tokens now into the tuple? and how do i parse them since i need to get multiple tokens
});
edit: typo
There are many ways to parse the data. You can use std::stringstream or find or whatever. I believe the question you are asking is how to store the values directly into a tuple. For that, use std::get which returns a reference to the value in the tuple.
// Parameter s is the line to parse. Ex: "Dolly Davenell,8809903417,1 Toban Circle,Luozhou"
std::tuple<std::string, long, std::string, std::string> parse_line(std::string s)
{
std::stringstream ss(s);
std::tuple<std::string, long, std::string, std::string> t;
if (std::getline(ss, std::get<0>(t), ',')) {
if (ss >> std::get<1>(t)) {
ss.ignore(1); // Skip comma
if (std::getline(ss, std::get<2>(t), ',') && std::getline(ss, std::get<3>(t))
return t;
}
}
}
// throw here or handle error somehow
}
I changed int to long as the value in the example is too large for 32-bit int.

Reading a input file into a vector

I'm trying to read a file of int's and double's into a vector but I am having difficulty doing so. Given something like:
1 2.1 3 4
2 4
3
9 0.1
How can I use ifstream and the getline function to convert the string into integers and doubles & inserting this into a vector?
I know this is incorrect but I am thinking of something along the lines of:
vector<Pair *> vec; //Pair is a class that contains a int & a double data member
string str;
double num;
ifstream f;
f.open("name of file");
while(getline(f, str){
num = stod(str);
}
To insert into the vector I believe I can do something along the lines of:
Pair * pairObj = new Pair(x,y); //"x" being of type int and "y" being of type double
v.push_back(pair);
I'm sorry if this is unclear, please let me know and I will do my best to explain myself.
You should just use stream iterators!
#include <iostream> // for IO
#include <vector> // for vector!
#include <iterator> // for stream iterator
#include <algorithm> // for copy (optional)
if you are directly initializing
vector<double>vdata{istream_iterator<double>(ifile),
istream_iterator<double>()};
else use copy or copy_n if you only want a fixed amount of data
copy(istream_iterator<double>(ifile),
istream_iterator<double(),
back_inserter(vdata));
if you are working with a large file i would recommend using this method
vector<doube>vdata;
// this will save alot of time, if you don't resize the vector must keep reallocating data
vdata.reserve(file_size);
copy(istream_iterator<double>(ifile),
istream_iterator<double>(),
back_inserter(vdata));
strtod() is C. Proper C++ uses the >> operator.
Once you have read each line of text, construct a std::istringstream from the string, then use operator>> to parse it.
Something along these line::
std::ifstream f("name of file");
// Check if the file was succesfully opened, etc...
std::string str;
while( getline(f, str))
{
std::istringstream i(str);
std::vector<double> v;
double d;
while (i >> d)
{
v.push_back(d);
}
if (!i.eof())
{
// Must be a parsing failure, deal with it in some way.
}
else
{
// Otherwise, v is the vector of numbers on this line.
}
}
string str;
std::vector< double> vd;
// loop reading lines of input
while( getline( f, str )
{
std::stringstream sst(str);
std::string a;
// loop reading space separated values in line
while( getline( sst, a, ' ' ) )
// conver to double and add to end of vectior
vd.push_back( stod( a );
}
// check for complete pairs
if( vd.size() % 2 )
cout << "Error!"
// loop over pairs
vector< pair<int,double> > vpairs;
for( int kp = 0; kp < vd.size()/2; kp++ )
vpairs.push_back( pair<int,double>( (int)vd[kp*2],vd[kp*2+1) );

Overload stream operator for nested template C++ STL

I've got a data file that is a single line consisting of a nested series of doubles eg.
[[0.127279,0.763675,0.636396],[0.254558,0.890955,0.636396],
[0.127279,0.636396,0.763675],[0.254558,0.763675,0.763675],
[0.381838,0.890955,0.763675],[0.127279,0.509117,0.890955],
[0.254558,0.636396,0.890955],[0.509117,0.890955,0.890955]]
I'd like to be able to read this into a STL vector<vector<double> > using the stream operator which is templated across the inner type of A:
vector<vector<double> > A;
FIN >> A;
I've figured out a way to do this when the vector is not nested, ie. a simple vector<T> as so:
template <class T>
istream& operator>>(istream& s, vector<T> &A){
T x;
string token; char blank;
s >> blank; // Gobble the first '['
while( getline(s, token, ',') ) {
istringstream input(token);
input >> x;
A.push_back(x);
}
s >> blank; // Gobble the last ']'
return s;
}
But I'm having problem with the istream& operator>>(istream& s, vector<vector<T> >&A) part as I can't seem to catch the inner ]'s properly. I'm sure that Boost has a way of doing this, but I'd like to see a solution with the STL for pedagogical purposes.
Note: I'm aware that overloading the stream operator for vector<T> can have far-reaching undesirable consequences and that the implementation should be wrapped up in its own class - I'm using this example above as it stands to clarify the question.
EDIT:
I'd like the method to be robust enough to handle a input array whose size (and inner array) size is not known in advance, but inferred from reading the stream.
Actually, the problem with your code that you want to use the same function for both, when T is:
vector<double>
double
But the logic which needs to read the data into vector and double is slightly different. So you cannot do that, at least not with that simple logic:
I would prefer to write two functions, to handle both cases separately. After all, even in your case, the compiler will generate two different functions for each value of T.
template <class T>
istream& operator>>(istream& s, vector<T> &A)
{
T x;
string token; char blank;
s >> blank; // Gobble the first '['
while( getline(s, token, ',') )
{
istringstream input(token);
input >> x;
A.push_back(x);
}
// s >> blank; // Gobble the last ']'
return s;
}
template <class T>
istream& operator>>(istream& s, vector<vector<T>> &A)
{
vector<T> x;
string token;
char blank;
s >> blank; // Gobble the first '['
while( getline(s, token, ']') )
{
istringstream input(token);
input >> x;
s >> blank; //read , after [...]
A.push_back(x);
x.clear();
}
s >> blank; // Gobble the last ']'
return s;
}
Test code:
int main() {
vector<vector<double>> A;
cin >> A;
for(size_t i = 0 ;i < A.size(); ++i)
{
for(size_t j = 0 ; j < A[i].size(); ++j)
cout << A[i][j] <<" ";
cout << endl;
}
return 0;
}
Input:
[[1,2,3],[4,5,6],
[7,8,9],[10,11,12],
[13,14,15],[16,17,18],
[19,20,21],[22,23,24]]
Output:
1 2 3
4 5 6
7 8 9
10 11 12
13 14 15
16 17 18
19 20 21
22 23 24
Online demo : http://ideone.com/iBbmw
In your particular example which is very simple.
Read the whole line into a string.
Replace all [ , ] and , with whitespace character.
Create a simple stringstream with whitespace replaced string.
Now you can have a a simple loop of
double x;
while( stringstreamp >> x )
{
}
And some special logic after reading three doubles to insert them them into a new array.
Some years later, and I was here struggling with the same problem.
Based on your contribution, I developed a modified version of the original template. This one is able to parse multidimensional arrays, even if they are spread across several lines.
template <class T>
istream& operator>>(istream& s, vector<T> &A){
while(true){
T x;
char c = s.peek();
if( c == '[' || c == ','){
s.ignore();
if(s >> x) A.push_back(x);
else throw invalid_argument("Bad, bad JS array!");
continue;
}
if( c == ']') {
s.ignore();
return s;
}
/* Ignore line-break */
s.ignore();
}
return s;
}
Hope this can be useful for someone.

How can I know whether my C++ string variable is a number or not

I have a string of class string
string str;
how can I check if it is a number or not, str can only have 3 possible types described below like
abcd
or a number like
123.4
or a number with a parenthesis attach to the end it for example
456)
note the parenthesis at the end of "str" is the only possible combination of number and none number
where the bottom two are considered valid numbers, I know I could use lexical_cast if only the first 2 cases occur, but how about considering all 3 possible cases to occur?
I don't need to do anything fancy with str, I just need to know whether it is a valid number as I described
The C++ solution for parsing strings manually is string streams. Put your string into a std::istringstream and read from that.
What you could do to parse this is to try to read an (unsigned) int from the string.
If this fails, it is a string not starting with digits.
If it works, peek at the next character. If that's a . you have a floating point number, if it's a ), you have an integer number. (Otherwise you have a reading error.)
Something along the lines of
void read(const std::string& str)
{
std::istringstream iss(str);
int i;
if( !(iss>>i) ) {
// str contains "abcd"
} else {
switch( iss.peek() ) {
case ')':
// i contains '456'
break;
case '.' {
double d;
if( !(iss>>d) ) throw "dammit!";
d += i;
// d contains floating point value
break;
default: throw "what?!";
}
// ...
}
Does this make sense?
You have to define "number". There isn't a generic "number" type (for example, your first number is a double while the second is an integer).
That said, as shown in this answer, lexical_cast simply checks that the destination and only the destination exists:
template <typename R, typename T>
R lexical_cast(const T& pX)
{
std::stringstream ss;
ss << pX;
R result; // take out any whitespace, make sure nothing is left
if ((ss >> result).fail() || !(ss >> std::ws).eof())
{
throw std::bad_cast();
}
return result;
}
So just make a new function:
template <typename R, typename T>
R weak_lexical_cast(const T& pX)
{
std::stringstream ss;
ss << pX;
R result;
if ((ss >> result).fail())
{
throw std::bad_cast();
}
return result;
}
It's basically a lexical_cast but doesn't care about any remaining characters (allowing your second number to work.)
maybe a regular expression would do the trick for you.

Compile errors while read/write size of multiple structs to file

I've already asked 2 questions kind of related to this project, and i've reached this conclusion. Writing the size of the Struct to the file , and then reading it back is the best way to do this.
I'm creating a program for a homework assignment that will allow me to maintain inventory. I need to read / write multiple structs of the same type to a file.
The problem is... this is really complicated and i'm having trouble wrapping my head around the whole process. I've seen a bunch of examples and i'm trying to put it all together. I'm getting compile errors... and I have zero clue on how to fix them. If you could help me on this I would be so appreciative... thank you. I'm so lost right now...
**** HOPEFULLY THE LAST EDIT #3 *************
My Code:
// Project 5.cpp : main project file.
#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
using namespace System;
using namespace std;
#pragma hdrstop
int checkCommand (string line);
template<typename Template>
void readFromFile(Template&);
template<typename Template>
void writeToFile(Template&);
template<typename T>
void writeVector(ofstream &out, const vector<T> &vec);
template<typename Template>
void readVector(ifstream& in, vector<Template>& vec);
struct InventoryItem {
string Item;
string Description;
int Quantity;
int wholesaleCost;
int retailCost;
int dateAdded;
} ;
int main(void)
{
cout << "Welcome to the Inventory Manager extreme! [Version 1.0]" << endl;
vector<InventoryItem> structList;
ofstream out("data.dat");
writeVector( out, structList );
while (1)
{
string line = "";
cout << endl;
cout << "Commands: " << endl;
cout << "1: Add a new record " << endl;
cout << "2: Display a record " << endl;
cout << "3: Edit a current record " << endl;
cout << "4: Exit the program " << endl;
cout << endl;
cout << "Enter a command 1-4: ";
getline(cin , line);
int rValue = checkCommand(line);
if (rValue == 1)
{
cout << "You've entered a invalid command! Try Again." << endl;
} else if (rValue == 2){
cout << "Error calling command!" << endl;
} else if (!rValue) {
break;
}
}
system("pause");
return 0;
}
int checkCommand (string line)
{
int intReturn = atoi(line.c_str());
int status = 3;
switch (intReturn)
{
case 1:
break;
case 2:
break;
case 3:
break;
case 4:
status = 0;
break;
default:
status = 1;
break;
}
return status;
}
template <typename Template>
void readFromFile(Template& t)
{
ifstream in("data.dat");
readVector(in, t); Need to figure out how to pass the vector structList via a Template
in.close();
}
template <typename Template>
void writeToFile(Template& t)
{
ofstream out("data.dat");
readVector(out, t); Need to figure out how to pass the vector structList via a Template
out.close();
}
template<typename T>
void writeVector(ofstream &out, const vector<T> &vec)
{
out << vec.size();
for(vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
{
out << *i; // SUPER long compile error
}
}
template<typename T>
vector<T> readVector(ifstream &in)
{
size_t size;
in >> size;
vector<T> vec;
vec.reserve(size);
for(int i = 0; i < size; ++i)
{
T tmp;
in >> tmp;
vec.push_back(tmp);
}
return vec;
}
My Compile Errors:
1>.\Project 5.cpp(128) : error C2679: binary '<<' : no operator found which takes a right-hand operand of type 'const InventoryItem' (or there is no acceptable conversion)
1> C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include\ostream(653): could be 'std::basic_ostream<_Elem,_Traits> &std::operator <<<char,std::char_traits<char>>(std::basic_ostream<_Elem,_Traits> &,const char *)'
1> with
That is the only error i'm getting now. I see your code is SO Much better. My new compiler error is SUPER long. I've shown where it the error points to. Could you help me just one last time?
Your read and write functions are buggy. In particular, you should be doing something like this instead:
template<typename T>
void write(ofstream &out, const T &t)
{
out << T;
}
OLD: bind1st requires you do include functional for it to work:
#include <functional>
Instead of dealing with all these functions and such, though, it'd be better to rely on iterators:
template<typename T>
void writeVector(ofstream &out, const vector<T> &vec)
{
out << vec.size();
for(vector<T>::const_iterator i = vec.begin(); i != vec.end(); ++i)
{
out << *i;
}
}
template<typename T>
vector<T> readVector(ifstream &in)
{
size_t size;
in >> size;
vector<T> vec;
vec.reserve(size);
for(int i = 0; i < size; ++i)
{
T tmp;
in >> tmp;
vec.push_back(tmp);
}
return vec;
}
You'd want functions to read and write your InventoryItem as well, probably:
ostream &operator<<(ostream &out, const InventoryItem &i)
{
out << i.Item << i.Description; // FIXME Read/write strings properly.
out << i.Quantity;
out << i.wholesaleCost << i.retailCost;
out << i.dateAdded;
}
istream &operator>>(istream &out, InventoryItem &i)
{
// Keep in same order as operator<<(ostream &, const InventoryItem &)!
in >> i.Item >> i.Description; // FIXME Read/write strings properly.
in >> i.Quantity;
in >> i.wholesaleCost >> i.retailCost;
in >> i.dateAdded;
}
NOTE: This is not an answer to the compilation errors you are getting, but rather a broader view of the persistence problem you are handling.
Serialization and deserialization is not the simplest problem you can work on. My advice would be investing in learning libraries (boost::serialization) and using them. They have already worked out many of the problems you will face at one time or another. Plus they already have different output formats (binary, xml, json...)
The first thing you must decide, that is if you decide to go ahead and implement your own, is what will be the file format and whether it suits all your needs. Will it always be used in the same environment? Will the platform change (32/64bits)? You can decide to make it binary as it is the simplest, or make it readable for a human being. If you decide on XML, JSON or any other more complex formats, just forget it and use a library.
The simplest solution is working on a binary file and it is also the solution that will give you a smallest file. On the other hand, it is quite sensible to architecture changes (say you migrate from a 32 to a 64 bit architecture/OS)
After deciding the format you will need to work on the extra information that is not part of your objects now but needs to be inserted into the file for later retrieval. Then start working (and testing) from the smallest parts to more complex elements.
Another advice would be to start working with the simplest most defined part and build from there on. Start avoiding templates as much as possible, and once you have it clear and working for a given data type, work on how to generalize it for any other type.
Disclaimer: I have written the code directly on the browser, so there could be some errors, typos or just about anything :)
Text
The first simple approach is just writting a textual representation of the text. The advantage is that it is portable and shorter in code (if not simpler) than the binary approach. The resulting files will be bigger but user readable.
At this point you need to know how reading text works with iostreams. Particularly, whenever you try to read a string the system will read characters until it reaches a separator. This means that the following code:
std::string str;
std::cin >> str;
will only read up to the first space, tab or end of line. When reading numbers (ints as an example) the system will read all valid digits up to the first non-valid digit. That is:
int i;
std::cin >> i;
with input 12345a will consume all characters up to 'a'. You need to know this because that will influence the way you persist data for later retrieval.
// input: "This is a long Description"
std::string str;
std::cin >> str; // Will read 'This' but ignore the rest
int a = 1;
int b = 2;
std::cout << a << b; // will produce '12'
// input: 12
int read;
std::cint >> read; // will read 12, not 1
So you pretty much need separators to insert in the output and to parse the input. For sample purposes I will select the '|' character. It must be a character that does not appear in the text fields.
It will also be a good idea to not only separate elements but also add some extra info (size of the vector). For the elements in the vector you can decide to use a different separator. If you want to be able to read the file manually you can use '\n' so that each item is in its own line
namespace textual {
std::ostream & operator<<( std::ostream& o, InventoryItem const & data )
{
return o << data.Item << "|" << data.Description << "|" << data.Quantity
<< "|" << data. ...;
}
std::ostream & operator<<( std::ostream & o, std::vector<InventoryItem> const & v )
{
o << v.size() << std::endl;
for ( int i = 0; i < v.size(); ++i ) {
o << v[i] << std::endl; // will call the above defined operator<<
}
}
}
For reading, you will need to split the input by '\n' to get each element and then with '|' to parse the InventoryItem:
namespace textual {
template <typename T>
void parse( std::string const & str, T & data )
{
std::istringstream st( str ); // Create a stream with the string
st >> data; // use operator>>( std::istream
}
std::istream & operator>>( std::istream & i, InventoryItem & data )
{
getline( i, data.Item, '|' );
getline( i, data.Description, '|' );
std::string tmp;
getline( i, tmp, '|' ); // Quantity in text
parse( tmp, data.Quantity );
getline( i, tmp, '|' ); // wholesaleCost in text
parse( tmp, data. wholesaleCost );
// ...
return i;
}
std::istream & operator>>( std::istream & i, std::vector<InventoryItem> & data )
{
int size;
std::string tmp;
getline( i, tmp ); // size line, without last parameter getline splits by lines
parse( tmp, size ); // obtain size as string
for ( int i = 0; i < size; ++i )
{
InventoryItem data;
getline( i, tmp ); // read an inventory line
parse( tmp, data );
}
return i;
}
}
In the vector reading function I have used getline + parse to read the integer. That is to guarantee that the next getline() will actually read the first InventoryItem and not the trailing '\n' after the size.
The most important piece of code there is the 'parse' template that is able to convert from a string to any type that has the insertion operator defined. It can be used to read primitive types, library types (string, for example), and user types that have the operator defined. We use it to simplify the rest of the code quite a bit.
Binary
For a binary format, (ignoring architecture, this will be a pain in the ass if you migrate) the simplest way I can think of is writing the number of elemements in the vector as a size_t (whatever the size is in your implementation), followed by all the elements. Each element will printout the binary representation of each of its members. For basic types as int, it will just output the binary format of the int. For strings we will resort to writting a size_t number with the number of characters in the string followed by the contents of the string.
namespace binary
{
void write( std::ofstream & o, std::string const & str )
{
int size = str.size();
o.write( &size, sizeof(int) ); // write the size
o.write( str.c_str(), size ); // write the contents
}
template <typename T>
void write_pod( std::ofstream & o, T data ) // will work only with POD data and not arrays
{
o.write( &data, sizeof( data ) );
}
void write( std::ofstream & o, InventoryItem const & data )
{
write( o, data.Item );
write( o, data.Description );
write_pod( o, data.Quantity );
write_pod( o, data. ...
}
void write( std::ofstream & o, std::vector<InventoryItem> const & v )
{
int size = v.size();
o.write( &size, sizeof( size ) ); // could use the template: write_pod( o, size )
for ( int i = 0; i < v.size(); ++i ) {
write( o, v[ i ] );
}
}
}
I have selected a different name for the template that writes basic types than the functions that write strings or InventoryItems. The reason is that we don't want to later on by mistake use the template to write a complex type (i.e. UserInfo containing strings) that will store an erroneous representation in disk.
Retrieval from disk should be fairly similar:
namespace binary {
template <typename T>
void read_pod( std::istream & i, T& data)
{
i.read( &data, sizeof(data) );
}
void read( std::istream & i, std::string & str )
{
int size;
read_pod( i, size );
char* buffer = new char[size+1]; // create a temporary buffer and read into it
i.read( buffer, size );
buffer[size] = 0;
str = buffer;
delete [] buffer;
}
void read( std::istream & i, InventoryItem & data )
{
read( i, data.Item );
read( i, data.Description );
read( i, data.Quantity );
read( i, ...
}
void read( std::istream & i, std::vector< InventoryItem > & v )
{
v.clear(); // clear the vector in case it is not empty
int size;
read_pod( i, size );
for ( int i = 0; i < size; ++i )
{
InventoryItem item;
read( i, item );
v.push_back( item );
}
}
}
For using this approach, the std::istream and std::ostream must be opened in binary mode.
int main()
{
std::ifstream persisted( "file.bin", ios:in|ios::binary );
std::vector<InventoryItem> v;
binary::read( persisted, v );
// work on data
std::ofstream persist( "output.bin", ios::out|ios::binary );
binary::write( persist, v );
}
All error checking is left as an exercise for the reader :)
If you have any question on any part of the code, just ask.
EDIT: Trying to clear up FUD:
bind1st is part of STL's functional header. STL existed before boost showed up. It is deprecated in C++0x in favor of the more generic version i.e. bind (aka boost::bind). See Annex D.8 Binders for more information.
Now the real problem (multiple edits may make this look silly, but I'll keep this for posterity's sake):
write<long>(out, structList.size());
This is the offending line. This expects a long as the second parameter, whereas the vector's size() is of type size_t or unsigned int under the hoods.
Update there was a typo: use size_t and not size_T:
write<size_t>(out, structList.size());
Next part:
for_each(structList.begin(), structList.end(), bind1st(write<InventoryItem>, out));
This should be structList or some other type. Also, include functional to be able to use bind1st. Add at the top:
#include <functional>
The template bind1st takes a functor. Passing around ordinary function pointers is not possible without some other hacks. You can use boost::bind as an alternative. Or:
for(InventoryItem::iterator i = structList.begin(), f = structList.end();
i != f; ++i)
write<InventoryItem>(out, *i);
Now for other nitpicks:
What is:
#include <String>
...
using namespace System;
Are you sure of what you are using here? If you want STL strings you need to include:
#include <string>
void main(void)
is not a standard signature. Use any one of:
int main(void)
or
int main(int argc, char *argv[]);
I/O is usually much easier with the predefined insertion/extraction operators. You can (and really should) use:
istream is(...);
is >> data;
and similarly
ostream os(...);
os << data;
Note also your readFromFile and writeToFile functions need to be fixed to use vector<InventoryItem> instead of vector simply.