A few hours ago I asked how I could read from a file with a specific format so I could program operator >>
The format of the file was:
Salad;Tomatoe 50;Fresh lettuce 100;Potatoe 60;Onion 10
Macaroni;Macaroni 250;Tomatoe 60;Oil 10
Fish and chips;fish 30;potatoe 30;Oil 40
And I have the following class:
...
#include <list> //I'm using list of the STL
....
class recipe{
private:
list<pair<string,unsigned int>> ing; //A list of the ingredients of one recipe. String for the name of the ingredient and unsigned int for the quantity of each ingredient
public:
....
//My solution for operator >>
istream & operator >> (istream &i, recipe &other){
string line, data, name_ing;
string code, nombre;
unsigned int plate, quantity;
list<pair<string,unsigned int>> ings;
getline(i,line);
stringstream s (line);
getline(s,data,';');
code = data;
getline(s,data,';');
plate = atoi(data.c_str());
getline(s,data,';');
name = data;
while(getline(s,data,' ')){
name_ing = data;
getline(s,data,';');
quantity = atoi(data.c_str());
pair<string,unsigned int> ingredient;
ingredient.first = name_ing;
ingredient.second = quantity;
ings.push_back(ingredient);
}
recipe a_recipe(code,plate,name,0,0,0,0,0,ings);
oher = a_recipe;
return i;
}
So now I have another problem, I don't know how to read those ingredients that are composed by two words, for example: "fresh lettuce 50", because the output would be:
Salad;Tomatoe 50;Fresh 0;Potatoe 60;Onion 10
It doesn't read Lettuce and the quantity. Any help?
As already written:
To solve the problem at hand there is a more or less standard approach. You want to read csv data.
In your case, it is a little bit more difficult, because you do have nested csv data. So first a ";" separated list and then a space separated list. The 2nd one is a little bit unprecise, because our ingredients coud have 2 spaces before the quantity, like in "Red pepper 2"
Now, how could this to be done? C++ is an object oriented language. You can create objects, consisting of data and member functions that operate on this data. We will define a class "Recipe" and overwrite the inserter and extractor operator. Because the class and only the class should know how this works. Having done that, input and output becomes easy.
The extractor, and that is the core of the question is, as said, a little bit more tricky. How can this be done?
In the extractor we will first read a complete line from an std::istream using the function std::getline. After having the line, we see a std::string containing "data-fields", delimited by a semicolon. The std::string needs to be split up and the "data-fields"-contents shall be stored. Additionally you need to split the ingredients.
The process of splitting up strings is also called tokenizing. The "data-fields"-content is also called "token". C++ has a standard function for this purpose: std::sregex_token_iterator.
And because we have something that has been designed for such purpose, we should use it.
This thing is an iterator. For iterating over a string, hence sregex. The begin part defines, on what range of input we shall operate, then there is a std::regex for what should be matched / or what should not be matched in the input string. The type of matching strategy is given with last parameter.
1 --> give me the stuff that I defined in the regex and
-1 --> give me that what is NOT matched based on the regex.
We can use this iterator for storing the tokens in a std::vector. The std::vector has a range constructor, which takes 2 iterators a parameter, and copies the data between the first iterator and 2nd iterator to the std::vector.
The statement
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), separator, -1), {});
defines a variable "token" of type std::vector<std::string>, splits up the std::string and puts the tokens into the std::vector. After having the data in the std::vector, we will copy it to the data members of our class.
For the 2nd split we create 2 simple lambdas and copy the data into the ingredients list.
Very simple.
Next step. We want to read from a file. The file conatins also some kind of same data. The same data are rows.
And as for above, we can iterate over similar data. If it is the file input or whatever. For this purpose C++ has the std::istream_iterator. This is a template and as a template parameter it gets the type of data that it should read and, as a constructor parameter, it gets a reference to an input stream. It doesnt't matter, if the input stream is a std::cin, or a std::ifstream or a std::istringstream. The behaviour is identical for all kinds of streams.
And since we do not have files an SO, I use (in the below example) a std::istringstream to store the input csv file. But of course you can open a file, by defining a std::ifstream csvFile(filename). No problem.
We can now read the complete csv-file and split it into tokens and get all data, by simply defining a new variable and use again the range constructor.
std::vector cookBook(std::istream_iterator<Recipe>(sourceFile), {});
This very simple one-liner will read the complete csv-file and do all the expected work.
Please note: I am using C++17 and can define the std::vector without template argument. The compiler can deduce the argument from the given function parameters. This feature is called CTAD ("class template argument deduction").
Additionally, you can see that I do not use the "end()"-iterator explicitely.
This iterator will be constructed from the empty brace-enclosed initializer list with the correct type, because it will be deduced to be the same as the type of the first argument due to the std::vector constructor requiring that.
Ì hope I could answer your basic question. Please see the full blown C++ example below:
#include <iostream>
#include <regex>
#include <string>
#include <list>
#include <vector>
#include <iterator>
#include <sstream>
// Data types for ingredients and quantity
using Ingredients = std::pair<std::string, int>;
// Some helper functions
auto trim = [](const std::string & s) { return std::regex_replace(s, std::regex("^ +| +$"), "$1"); };
auto split = [](const std::string & s) {size_t pos{ s.rfind(' ') }; return Ingredients(s.substr(0, pos), std::stoi(s.substr(pos))); };
std::regex separator{ ";" };
// Our recipe class
struct Recipe {
// data
std::string title{};
std::list<Ingredients> ingredients{};
// Overwrite extractor
friend std::istream& operator >> (std::istream& is, Recipe& r) {
// We will read one line into this temproary
std::string line{};
if (std::getline(is, line)) {
// Tokenize the base string
std::vector token(std::sregex_token_iterator(line.begin(), line.end(), separator, -1), {});
// get the recipe title
r.title = token[0];
// And, get the ingredients
r.ingredients.clear();
std::transform(std::next(token.begin()), token.end(), std::back_inserter(r.ingredients),
[](const std::string& s) { return split(trim(s)); });
}
return is;
}
// Overwrite inserter
friend std::ostream& operator << (std::ostream& os, const Recipe& r) {
// Print one recipe
os << "---- Recipe: " << r.title << "\n-- Ingredients:\n\n";
for (const auto& [ingredient, quantity] : r.ingredients)
os << ingredient << " --> " << quantity << "\n";
return os;
}
};
// Source file with CSV data. I added "Red Pepper 2" to Salad
std::istringstream sourceFile{ R"(Salad;Tomatoe 50;Lettuce 100;Potatoe 60;Red Pepper 2;Onion 10
Macaroni;Macaroni 250;Tomatoe 60;Oil 10
Fish and chips;fish 30;potatoe 30;Oil 40)" };
int main() {
// Read all data from the file with the following one-liner
std::vector cookBook(std::istream_iterator<Recipe>(sourceFile), {});
// Show some debug output
std::copy(cookBook.begin(), cookBook.end(), std::ostream_iterator<Recipe>(std::cout, "\n"));
return 0;
}
Again:
What a pity that nobody will read this . . .
I suggest that you make a type out of the ingredient and amount part instead of using std::pair<std::string, unsigned>. With that you can add stream operators for that type too (and not risk it being used by a different std::pair<std::string, unsigned> than the one you want to support). It breaks the problem down somewhat and makes it simpler to implement / understand.
That being that said, I suggest that you use something else than a space as a delimiter between the ingredient name and the amount since that complicates the parsing (as you can see in the code).
Here's an example with comments:
#include <cstdlib>
#include <iostream>
#include <list>
#include <sstream>
#include <string>
#include <tuple>
// a simple ingredient type
struct ingredient {
std::string name{};
unsigned amount{};
};
// read an ingredient "<name> <amount>"
std::istream& operator>>(std::istream& is, ingredient& i) {
std::string entry;
if(std::getline(is, entry, ';')) { // read until ; or EOL
// find the last space in "entry"
if(size_t pos = entry.rfind(' '); pos != std::string::npos) {
// extract the trailing amount
if(unsigned am = static_cast<unsigned>(
// Create a substring from the last space+1 and convert it to an
// unsigned (long). The static_cast<unsigned> silences a warning about
// the possibility to get the wrong value if it happens to be larger
// than an unsigned can hold.
std::strtoul(entry.substr(pos + 1).c_str(), nullptr, 10));
// and check that we extracted something else than zero
am != 0)
{ // extracted the amount successfully
i.name = entry.substr(0, pos); // put the name part in i.name
i.amount = am; // and the amount part in i.amount
} else { // extracting the amount resulted in 0
// set failbit state on is
is.setstate(std::ios::failbit);
}
} else { // no space found, set failbit
is.setstate(std::ios::failbit);
}
}
return is;
}
// output an ingredient
std::ostream& operator<<(std::ostream& os, const ingredient& i) {
return os << i.name << " " << i.amount;
}
class recipe {
public:
std::string const& name() const { return rname; }
// convenience iterators to iterate over ingreidiences, const
auto begin() const { return ing.cbegin(); }
auto end() const { return ing.cend(); }
// non-const if you'd like to be able to change an ingredient property while iterating
auto begin() { return ing.begin(); }
auto end() { return ing.end(); }
private:
std::list<ingredient> ing{}; // the new type in use
std::string rname{}; // recipe name
friend std::istream& operator>>(std::istream&, recipe&);
};
std::istream& operator>>(std::istream& i, recipe& other) {
std::string line;
if(std::getline(i, line)) {
std::istringstream ss(line);
if(std::getline(ss, other.rname, ';')) {
// only read the recipe's name here and delegate reading each ingredient
// to a temporary object of your new ingredient type
other.ing.clear(); // remove any prior ingrediences from other
ingredient tmp;
while(ss >> tmp) { // extract as normal
other.ing.push_back(tmp); // and put in ing if successful
}
}
}
return i;
}
// output one recipe in the same format as it can be read
std::ostream& operator<<(std::ostream& os, const recipe& other) {
os << other.name();
for(auto& i : other) {
os << ';' << i;
}
return os << '\n';
}
int main() {
std::istringstream is(
"Salad;Tomatoe 50;Fresh lettuce 100;Potatoe 60;Onion 10\n"
"Macaroni;Macaroni 250;Tomatoe 60;Oil 10\n"
"Fish and chips;fish 30;potatoe 30;Oil 40\n");
recipe r;
while(is >> r) {
std::cout << r;
}
}
Related
I am doing a project in my class that requires the program to stores teams into a vector. So I would have something like this:
string bballteam;
vector<string> teamVector;
and then I want the user to input team names. So I can just prompt
cin >> bballTeam;
teamVector.push_back(bballTeam);
to store the user input into the vector. However, I would like for the user input to be an array so that I can store players (like a roster). What are some ways I can implement this?
I acknowledge that you can't have arrays in a vector.
What you need to know is the following:
If you have many different attributes for one Object, then a struct or class must be used. If you have for example a "Player", then you would put the attributes of "Player" in a struct. Like this:
struct Player {
std::string firstName{};
std::string name {};
unsigned int age{};
double score{};
};
Here you have one Type with many attributes or properties.
If you need many "Objects" of same Type, then you would store those in an "array" type, like a C-Style array, or an std::array, or, if you need some variable size, a std::vector.
So, again
Many attributes of different Type will be stored in a struct
Many obejects with the same Type will be stored in n array.
Of course all these structures or arrays can be nested. You may have an array of struct or a struct containing arrays.
This depends on how you want to abstract the reality. It is about "relations".
For the input: Objects of the same Type are entered in "loops", e.g. a for or a while loop.
Object attributes of different Type will be entered one after the other.
If you have a struct or class and want to enter all its attributes with a simple extraction (>>) operation, then you will observe that the existing extract operator >> does not know, how to work with your Type. That, because you defined a new Type, for example "Player". So we need to tell the compiler how to "Get" the data. And for that, we need to define the input functionality for the new Type. This is done with so called operator overloading. Sorry, you need to read about that. But, it is simple for input and for output. Within a struct you can normally use the following function signature:
struct Player {
std::string firstName{};
std::string name {};
unsigned int age{};
double score{};
// Extraction (input)
friend std::istream& operator >> (std::istream& is, Player& p);
// Insertion (output)
friend std::ostream& operator << (std::ostream& os, const Player& p);
};
You need to program the details of these functions.
So, next, the Team. Here you need many objects of the same Type, so many "Players".
You could write std::vector<Player> team{}; to define a team having many players.
And a complete example for the stuff learned so far:
#include <iostream>
#include <string>
#include <vector>
#include <iomanip>
struct Player {
std::string firstName{};
std::string name{};
unsigned int age{};
double score{};
// Extraction (input)
friend std::istream& operator >> (std::istream& is, Player& p) {
std::getline(is >> std::ws, p.firstName);
std::getline(is, p.name);
return is >> p.age >> p.score;
}
// Insertion (output)
friend std::ostream& operator << (std::ostream& os, const Player& p) {
return os << p.firstName << ' ' << p.name << ' ' << p.age << ' ' << p.score;
}
};
int main() {
// Define the team
std::vector<Player> team{};
// Give user instructions and get number of players for input
std::cout << "How many players do you want to enter?\n";
int numberOfPlayers{}; std::cin >> numberOfPlayers;
// Get the player data and add to team
for (int i = 0; i < numberOfPlayers; ++i) {
std::cout << "\n\nEnter player data << " << i + 1 << " One at a line : First name, name, age, score:\n";
Player player;
std::cin >> player;
team.push_back(player);
}
// Show complete team:
std::cout << "\n\nTeam:\n";
for (const Player& p : team)
std::cout << p << '\n';
}
Since you did not state your requirements very clearly, I cannot answer further. But you should have understood the basics now and can add further structs and arrays as needed.
Happy coding
The one annoyance I have with the std::string class is that the only ways (that I could figure out after reading cplusplus.com's info about it) to get the location of the last character in the string is (stringVariable.length() - 1) or stringVariable.rfind(stringVariable.back()). It'd be nice if I could just do stringVariable.last(). This is for a function I wrote in a personal project that needs to reference this number of a user's input string.
Some ideas I had were creating a struct that is just a string and has the last() function as a member function, or a class that copies the string class and adds that member. With a struct, I'm not sure how (or if it's possible) to make it so I can make the variable assigned to the type automatically reference the string in it so that I don't need to make a constant member call. I'm taking the second C++ class in school right now, but we haven't touched on C++ classes yet.
I tried reading some guides and answers here about classes, and they don't look all that different from structs, but the more complex stuff in either case was Greek to me. I only learned how to use a struct as a holder for multiple variable types; like the name string and id int for a person, or something like that. I learned from the guides I can put functions in them, and define operator behavior too, but when I tried to define operator behavior for the string struct I made, I couldn't get it to work.
Based on the example I read, I tried:
str &operator=(const str &ing)
{
s = ing.s;
return s;
}
But when I tried to test it by using = to copy a str variable to a string variable I could cout, it errors that str can't be converted to string, so I tried a few adjustments, and after getting errors about the & and such, I ended up with:
string operator=(str ing)
{
return ing.s;
}
Which gets the same error. Here's the full struct test program I'm using:
#include <iostream>
#include <string>
using namespace std;
struct str
{
string s;
string operator=(str ing)
{
return ing.s;
}
int last()
{
return s.length() - 1;
}
};
int main()
{
str ing {"Hello World!"};
string i = ing;
cout << i;
return 0;
}
If I could get the = assignment to work, I'd try to get << and such to work with str, too.
If that doesn't turn out to be possible, my fallback is just using a function like:
int last(string str)
{
return str.length() - 1;
}
Edit: For More Info
I was asked many times in the comments what my actual need is for this. In truth it’s just that it annoys me that it doesn’t have that member function. I’m surprised it wasn’t implemented long ago.
Thanks to the commenters I was able to come up with the diabolical workaround of overloading an operator to make a string able to subtract a function to output the result of a string being input to the function. I used - because I was thinking script notation: stringVariable -l or stringVariable -last. Then I can also just use #define to remove the need for the operator, making it stringVariable last() (“last()” instead of “last” because aesthetic).
Another option I figured out was reversing positions and using #define to make it last stringVariable, or #define in - to make it last in stringVariable. Although the simplest version of the operator function for this by far is just defining a negative string as the int output directly. Not that any of that is really better than just using the function normally, but I enjoyed learning about those features of C++.
The single answerer also consolidated much of the commenters advice and brought it to the next level to give me a more elegant version of the idea I had to create my own class with a string that could do all the string stuff and the extra member. However taking the advice of reexamining my needs I think perhaps ‘string’ isn’t the only option I have for a user input container. I definitely need the int output made by stringVariable.size() - 1 with whatever char array I use (since it’s used in mathematical equations), but maybe a vector or something else could do that already?
I was briefly told about vectors in my previous class, but we weren’t allowed to use them. Since this is for a simulated environment I’m doing on my own (that I might use for the basis of a game some day) I can use anything I want though. I also decided to pull an allnighter last night and just started reading about containers at random on cplusplus.com.
So I’m wondering if perhaps a list or deque could work too. I wasn’t really taught about anything other than string and hardcoded arrays (yet) though. I use the string to store the user’s input to simplify the process of avoiding unforeseeable crashing/errors that come from bad user inputs, then just translate the strings to whatever I need with various functions I've written.
creating a struct that is just a string and has the last() function as a member function
That will work just fine, if you don't mind implementing the interfaces needed to convert between std::string <-> str.
Also, be aware that std::string indexes use std::string::size_type, which is not int. size_type is an unsigned type, typically std::size_t. std::string defines an npos constant to indicate "no index", that is what your last() function should return if the std::string is empty, eg:
std::string::size_type last_index() const
{
return !s.empty() ? s.size() - 1 : std::string::npos;
}
But, if you really want to have last() return an int (ie, your std::string will never exceed 2147483647 characters):
int last_index() const
{
return static_cast<int>(s.size()) - 1;
}
With a struct, I'm not sure how (or if it's possible) to make it so I can make the variable assigned to the type automatically reference the string in it so that I don’t need to make a constant member call.
In short, no.
Based on the example I read, I tried:
str &operator=(const str &ing)
{
s = ing.s;
return s;
}
It needs to return a reference to the str object that is being assigned to, not the std::string member that was actually modified, eg:
str& operator=(const str &rhs)
{
s = rhs.s;
return *this;
}
But when I tried to test it by using = to copy a str variable to a string variable I could cout, it errors that str can't be converted to string
Correct, it can't by default. You need to define a conversion operator for that purpose, eg:
operator std::string() const
{
return s;
}
If I could get the = assignment to work, I'd try to get << and such to work with str, too.
Yes, you should, eg:
std::ostream& operator<<(std::ostream &out, const str &rhs)
{
return out << rhs.s;
}
If that doesn't turn out to be possible, my fallback is just using a function
That is a good idea, eg:
std::string::size_type last_index(const std::string &s)
{
return !s.empty() ? s.size() - 1 : std::string::npos;
}
Or:
int last_index(const str::string &s)
{
return static_cast<int>(s.size()) - 1;
}
Here's the full struct test program I'm using
Try this:
#include <iostream>
#include <string>
using namespace std;
class str
{
private:
string s;
public:
str() = default;
str(const string &s) : s(s) {}
str& operator=(const str &rhs)
{
s = rhs.s;
return *this;
}
operator string() const
{
return s;
}
string::size_type last_index() const
{
return !s.empty() ? s.size() - 1 : string::npos;
}
friend ostream& operator<<(ostream &out, const str &rhs)
{
return out << rhs.s;
}
};
string::size_type last_index(const string &s)
{
return !s.empty() ? s.size() - 1 : string::npos;
}
int main()
{
str ing {"Hello World!"};
cout << ing << endl;
cout << ing.last_index() << endl;
string i = ing;
cout << i << endl;
cout << last_index(i) << endl;
return 0;
}
"Write a class named Person that represents the name and address of a person. Use a string to hold each of these elements. Add operations to read and print Person objects to the code you wrote."
Note I haven't reached the section on access control yet.
#include <iostream>
#include <string>
using std::string;
struct Person
{
string name_var, address_var;
};
std::ostream &print(std::ostream&, const Person &);
std::istream &read(std::istream&, Person &);
std::istream &read(std::istream &is, Person &item)
{
is >> item.name_var >> item.address_var;
return is;
}
std::ostream &print(std::ostream &os, Person &item)
{
os << item.name_var << " " << item.address_var;
return os;
}
With this I can only read single worded names and addresses if I use std::cin as the first argument to read, which isn't very useful. Can you somehow use getline?
You can use:
std::getline(is, item.name_var);
You can also specify a delimiter char as the third argument
Look at one thing: you have used a space to separate name and address. Now, in order to know when the name is over, you need to use some other delimiter for name. Another delimiter means you need to enclose the name in e.g. double quote (") and then, while reading the name, get input until you get the second delimiter.
I want to serialize an object of type Person. I want to use it later on for data saving or even game saving. I know how to do it for primitives like int, char, bool, and even c-strings like char[].
The problem is, I want the string to be as big as it needs to rather than declaring a char array of size 256 and hoping no one enters something too big. I read that serializing a class with std::string as a member doesn't work because it has an internal pointer, but is there a way to serialize my class which has a char* as a member?
I realize Boost has a serialization library, but I'd like to do this without the need of external libraries, it seems like a good activity to try.
Here's my Person class:
class Person
{
private:
char* _fname;
char* _lname;
public:
Person();
Person(const char* fname, const char* lname);
Person(const string& fname, const string& lname);
string fname() const;
void fname(const char* fname);
void fname(const string& fname);
string lname() const;
void lname(const char* lname);
void lname(const string& lname);
};
First: Use std::string in your class it will make your life so much easier in the long run.
But this advice works for both std::string and char* (with minor tweaks that should be obvious).
Basically you want to serialize data of unknown size (at compile time). This means when you de-serialize the data you must either have a technique that tells you how long the data is (prefix the object with a size) or a way to find the end of the data (a termination marker).
A termination marker is easier for serialization. But harder for de-serialization (as you must seek forward to find the end). Also you must escape any occurrences of the termination marker within your object and the de-serialization must know about the escaping and remove it.
Thus because of this complications I prefer not to use a termination marker. As a result I prefix the object with a size. The cost of this is that I must encode the size of the object in a way that will not break.
So if we prefix an object with its size you can do this:
// Place a ':' between the string and the size.
// There must be a marker as >> will continue reading if
// fname contains a digit as its first character.
// I don;t like using a space as >> skips spaces if you are not carefull
// and it is hard to tell the start of the string if the first characters in fname
// are the space character.
std::cout << strlen(fname) << ":" << fname;
Then you can de-serialize like this:
size_t size;
char mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{ throw BadDataException;
}
result = new char[size+1](); // Note the () to zero fill the array.
std::cin.read(result, size)
Edit 1 (based on comments) Update: to use with string:
size_t size;
char mark;
std::cint >> size >> mark;
if (!std::cin || mark != ':')
{ throw BadDataException;
}
std::string result(' ', size); // Initialize string with enough space.
std::cin.read(&result[0], size) // Just read directly into the string
Edit 2 (based on commented)
Helper function to serialize a string
struct StringSerializer
{
std::string& value;
StringSerializer(std::string const& v):value(const_cast<std::string&>(v)){}
friend std::ostream& operator<<(std::ostream& stream, StringSerializer const& data)
{
stream << data.value.size() << ':' << data.value;
}
friend std::istream& operator>>(std::istream& stream, StringSerializer const& data)
{
std::size_t size;
char mark(' ');
stream >> size >> mark;
if (!stream || mark != ':')
{ stream.setstate(std::ios::badbit);
return stream;
}
data.value.resize(size);
stream.read(&data.value[0], size);
}
};
Serialize a Person
std::ostream& operator<<(std::ostream& stream, Person const& data)
{
return stream << StringSerializer(data.fname) << " "
<< StringSerializer(data.lname) << " "
<< data.age << "\n";
}
std::istream& operator>>(std::istream& stream, Person& data)
{
stream >> StringSerializer(data.fname)
>> StringSerializer(data.lname)
>> data.age;
std::string line;
std::getline(stream, line);
if (!line.empty())
{ stream.setstate(std::ios::badbit);
}
return stream;
}
Usage:
int main()
{
Person p;
std::cin >> p;
std::cout << p;
std::ofstream f("data");
f << p;
}
You can't serialize pointer, you need to serialize data pointer points to.
You'll need to serialize whole web of objects, starting from Person (or Game) and looking into each object, which is reachable from your start object.
When deserializing, you reading data from your storage, allocate memory for that data and use address of this freshly allocated memory as a member of Person/Game object
Pointer fields make it bit harder, but not impossible to serialize. If you don't want to use any of the serialization libraries, here is how you can do it.
You should determine the size of what is pointed to at the time of serialization (e.g. it may be of fixed size or it may be a C-string with null character at the end), then you can save a mark indicating that you're serializing an indirect object together with size and the actual content of the area pointed to.
When you stumble upon that mark during deserialization, you can allocate the right amount of memory, copy the object into it and store the pointer to the area in the deserialized object.
I recommend using a vector to encapsulate strings for
serialization.
#include <vector>
using namespace std;
map vector<unsigned char> cbuff;
inline cbuff vchFromString(const std::string &str) {
unsigned char *strbeg = (unsigned char*) str.c_str();
return cbuff(strbeg, strbeg + str.size());
}
inline std::string stringFromVch(const cbuff &vch) {
std::string res;
std::vector<unsigned char>::const_iterator vi = vch.begin();
while (vi != vch.end()) {
res += (char) (*vi);
vi++;
}
return res;
}
class Example
{
cbuff label;
Example(string labelIn)
{
SetLabel(labelIn);
}
IMPLEMENT_SERIALIZE
(
READWRITE(label);
)
void SetLabel(string labelIn)
{
label = vchFromString(labelIn);
}
string GetLabel()
{
return (stringFromVch(label));
}
};
I want to read and parse a text file in C++ in a generic way. The file is always made of key-value pairs, one per line. The key is templated as well as the value. I foresee the key and the values to always be a basic type (int, float, string).
My problem is that I don't know how to create a transform the key or value string into the correct type.
I tried the following :
template<class Key, class T> inline
void EventReportReader<Key, T>::validateFileFormat()
{
// Read file line by line and check that the first token is of type Key and the second one of type T
std::string line;
try {
boost::regex re( "(\\S+)\\s+(.*)" );
while( getline( inStream_, line ) ) {
boost::cmatch matches;
if( boost::regex_match( line.c_str(), matches, re ) ) {
std::cout << re << " matches " << line << std::endl;
std::cout << " 1st : " << matches[1] << "\n 2nd : " << matches[2] << std::endl;
// test types
Key *k = dynamic_cast<Key*>(&matches[1]);
T t = dynamic_cast<T>(matches[2]);
}
}
}
catch( boost::regex_error& e ) {
// todo problem with regular expression, abort
}
}
And the use of this method is as follow :
// This in turn calls the method validateFileFormat
EventReportReader<float, int> reader( testFileName );
The result is
/home/vonhalle/dev/EventBasedReport/libs/event_based_report/EventReportReader.h:121:60: error: cannot dynamic_cast ‘(const boost::sub_match*)matches.boost::match_results::operator[] with BidiIterator = const char*, Allocator = std::allocator >, boost::match_results::const_reference = const boost::sub_match&’ (of type ‘const struct boost::sub_match’) to type ‘float’ (target is not pointer or reference to class)
/home/vonhalle/dev/EventBasedReport/libs/event_based_report/EventReportReader.h:122:53: error: cannot dynamic_cast ‘matches.boost::match_results::operator[] with BidiIterator = const char*, Allocator = std::allocator >, boost::match_results::const_reference = const boost::sub_match&’ (of type ‘const struct boost::sub_match’) to type ‘int’ (target is not pointer or reference)
How should I do it ?
Is it even possible ?
EDIT:
The file might look like this if the template is < float, int >
1.14 5
2.34 78
0.56 24
or this if the template is < int, string >
23 asdf
45 2222
1 bbbb
EDIT2:
The problem statement above is partially wrong. The key is never a string, the value can be a string. Therefore, whatever is before the first space is the key and the rest is the value. Sorry about this mistake.
I think your basic approach is wrong.
You seem to be trying to use template-meta programming to achieve your goals.
This is probably not a good idea.
A simpler approach is just to use C++ streams.
These stream objects already know how to read all the basic types. And anybody that want to do anything in C++ will add the appropriate input and output operators to stream their class; so it is pretty universal that you will be able to read any type as both key and value (with the restriction that it must fit on one line).
So now you just need to use standard template logic to define an operator that will read two objects of different types on a single line.
Try this:
#include <string>
#include <memory>
#include <fstream>
#include <sstream>
#include <vector>
#include <iterator>
#include <algorithm>
// These can be any types.
typedef std::string Key;
typedef int Value;
// The data type to hold the data.
template<typename K,typename V>
class Data: public std::pair<K, V>
{
};
Here is the code that will read one record from one line of the file:
Note that the data type Data and this input operator are both templated and can thus ready Key/Value pairs of any objects (as long as those objects know how to stream themselves).
template<typename K,typename V>
std::istream& operator>>(std::istream& stream, Data<K,V>& data)
{
// Read a line into a local string.
std::string line;
std::getline(stream,line);
// convert the line into a stream and read the key/value from the line
std::stringstream linestream(line);
linestream >> data.first >> data.second;
// If the linestream is bad, then reading the key/value failed
// If reading one more `char` from the linestream works then there is extra crap in the line
// thus we have bad data on a line.
//
// In either case set the bad bit for the input stream.
char c;
if ((!linestream) || (linestream >> c))
{
stream.setstate(std::ios::badbit);
}
// return the stream.
return stream;
}
Now using it simply means using a stream:
int main()
{
// The input file
std::ifstream file("Plop");
// We will convert the file and store it into this vector.
std::vector<Data<Key,Value> > data;
// Now just copy the data from the stream into the vector.
std::copy(std::istream_iterator<Data<Key,Value> >(file),
std::istream_iterator<Data<Key, Value> >(),
std::back_inserter(data)
);
}
Note: In the above example a key must be a single word (as it is read using a string). If you want a key as a string that contains a space you need to do some extra work. But that is the subject of another question.
dynamic_cast is to be used in inheritance hierarchies; you're misusing it here.
For such a simple task, you could use streams, I think (warning, untested):
template<class Key, class T>
void EventReportReader<Key, T>::validateFileFormat()
{
std::string line;
while( getline( inStream_, line ) ) {
std::istringstream stream(line);
Key k; T t;
stream >> k >> t;
if (!stream || !stream.eof()) { /*error*/ }
}
}
Is there a reason you are not actually keeping the values you read ?
EDIT: Let's specialize for strings
template<class Key>
void EventReportReader<Key, std::string>::validateFileFormat()
{
std::string line;
while( getline( inStream_, line ) ) {
size_t const ws = line.find(' ');
if (ws == std::string::npos) { /* error */ }
std::istringstream stream(line.substr(0, ws));
Key k;
stream >> k;
if (!stream) { /*error*/ }
std::string t = line.substr(ws);
boost::trim(t);
}
}
Without information you can't be sure whether you got the right type or not. Your value could be a string or an int. For example you could parse it with this algorithm:
Parse it as int, if it's not successful
Parse it as float, if not
It's a string
Again, you could have a successfully parsed int, but you expect a string.
Concerning your implementation: You probably want partial template specialication.
template<class Key> inline
void EventReportReader<Key, int>::validateFileFormat()
{
// readLine
// parse as int..
}
template<class Key> inline
void EventReportReader<Key, float>::validateFileFormat()
{
// readLine
// parse as float..
}
But it's probably better not to template your function? If you have a "normal" function you can have a parse logic I described before..