Overriding operator>> for a Strings class - c++

I just have a quick question. I need to override the operator >> for a custom String class and I can't quite figure out how to do it.
I know that this code works, because it was my original method of solving the problem:
istream& operator>>(istream &is, String &s) {
char data[ String::BUFF_INC ]; //BUFF_INC is predefined
is >> data;
delete &s;
s = data;
return s;
}
However, according to the spec (this is a homework assignment), I need to read in the characters 1 at a time to manually check for whitespace and ensure that the string isn't too big for data[]. So I changed my code to the following:
istream& operator>>(istream &is, String &s) {
char data[ String::BUFF_INC ];
int idx = 0;
data[ 0 ] = is.get();
while( (data[ idx ] != *String::WHITESPACE) && !is.ios::fail() ) {
++idx;
is.get();
data[ idx ] = s[ idx ];
}
return is;
}
When this new code is executed however it just gets stuck in a loop of user input. So how do I use is.get() to read in the data character by character but not wait for more user input? Or should I perhaps be using something other than .get()?

You don't seem to be doing anything with the character you get from the stream
istream& operator>>(istream &is, String &s) {
char data[ String::BUFF_INC ];
int idx = 0;
data[ 0 ] = is.get();
while( (data[ idx ] != *String::WHITESPACE) && !is.ios::fail() ) {
++idx;
is.get(); // you don't do anything with this
data[ idx ] = s[ idx ]; // you're copying the string into the buffer
}
return is;
}
So it checks whether the string s contains a whitespace, not whether you read a whitespace from the stream.

Try:
istream& operator>>(istream &is, String &s)
{
std::string buffer;
is >> buffer; // This reads 1 white space separated word.
s.data = buffer.c_str();
return is;
}
Commenting on your original code:
istream& operator>>(istream &is, String &s)
{
char data[ String::BUFF_INC ];
is >> data; // Will work. But prone to buffer overflow.
delete s; // This line is definately wrong.
// s is not a pointer so I don;t know what deleting it would do.
s = data; // Assume assignment operator is defined.
// for your class that accepts a C-String
return s;
}
Using the second version as a base:
istream& operator>>(istream &is, String &s)
{
std::vector<char> data;
char first;
// Must ignore all the white space before the word
for(first = is.get(); String::isWhiteSpace(first) && is; first = is.get())
{}
// If we fond a non space first character
if (is && !String::isWhiteSpace(first))
{
data.push_back(first);
}
// Now get values while white space is false
char next;
while( !String::isWhiteSpace(next = is.get()) && is)
{
// Note we test the condition of the stream in the loop
// This is because is.get() may fail (with eof() or bad()
// So we test it after each get.
//
// Normally you would use >> operator but that ignores spaces.
data.push_back(next);
}
// Now assign it to your String object
data.push_back('\0');
s.data = data;
return is;
}

Related

Matching a string at the beginning of a inputstream

I have implemented a simple inputstream manipulator to match the next n chars in an inputstream against a given string. However, I am not sure if this is the best way to do this. Any hints?
class MatchString {
private:
std::string mString;
public:
MatchString(const std::string &str) {
mString = str;
}
std::istream& operator()(std::istream& is) const {
// Allocate a string buffer, ...
char *buffer = new char[mString.length()];
// ... read next n chars into the buffer ...
is.read(buffer, mString.length());
// ... and compare them with given string.
if(strncmp(buffer, mString.c_str(), mString.length())) {
throw MismatchException(mString);
}
delete[] buffer;
return is;
}
};
inline MatchString match(const std::string &str) {
return MatchString(str);
}
inline std::istream& operator>>(std::istream& is, const MatchString& matchStr) {
return matchStr(is);
}
EDIT:
A solution consuming the matched chars could be implemented based on the suggestion of user673679:
class MatchString {
...
std::istream& operator()(std::istream& is) const {
// Match the next n chars.
std::for_each(mString.begin(), mString.end(),
[&](const char c) {
if(is.get() != c) {
throw MismatchException(mString);
}
});
return is;
}
};
How would I implement this if I don't want to consume the chars?
EDIT II:
Here another solution mentioned by fjardon:
class MatchString {
...
std::istream& operator()(std::istream& is) const {
// Match the next n chars.
if(std::mismatch(mString.begin(), mString.end(),
std::istreambuf_iterator<char>(is)).first != mString.end()) {
throw MismatchException(mString);
}
return is;
}
};
EDIT III:
Finally got a working function, that will revert consumption, if string doesn't match:
class MatchString {
...
std::istream& operator()(std::istream& is) const {
// Match the next n chars.
std::streampos oldPos = is.tellg();
if(std::mismatch(mString.begin(), mString.end(),
std::istreambuf_iterator<char>(is)).first != mString.end()) {
is.seekg(oldPos);
throw MismatchException(mString);
}
return is;
}
};
Instead of allocating and copying the whole string from the stream, you could just check one character at a time and avoid allocating the buffer completely:
#include <iostream>
#include <sstream>
#include <string>
auto mString = std::string("foobar");
std::istream& match(std::istream& is) {
for (auto c : mString)
if (c != is.get())
throw std::runtime_error("nope");
return is;
}
int main()
{
auto input = "foobarbaz";
auto stream = std::istringstream(input);
match(stream);
std::cout << "done!" << std::endl;
}
You should also add error checking for is.get() (or .read() in your original code).

Parse Comma Delimited File without using <string>

I have a file which looks like:
123,Cheese,Butter
78,Milk,Vegetable,Fish
and I wish to read each line into a data type List which has int num and char things[3][10] using overloaded operator >>. So far I have:
friend istream& operator>> (istream &is, List &rhs)
{
char comma;
is >> rhs.num >> comma >> ... (I don't know how to continue)
return is;
} // operator>>
Am I doing it right using char comma to skip a comma? How do I read different entries with different lengths separated by comma without using string?
It will be only a pseudocode but if you really need to avoid std::string your best choice is to make it more or less look like this:
istream &operator >>(istream &s, YourType &mylist) {
char mybuf[256];
s.read(mybuf, 256);
char *beg = mybuf;
char *cur = beg;
while (cur != mybuf + 256 && *cur!=0) {
if (*cur == '\n') {
mylist.addnext();
}
if (*cur == ',') {
*cur = 0; //to make the char string end on each comma
mylist.current.add(beg);
beg = cur + 1;
}
}
}
Remember that if YourType will be for example vector<vector<const char *>> you will need to add the operator >> into the std namespace.

How to cleanly extract a string delimited string from an istream in c++

I am trying to extract a string from an istream with strings as delimiters, yet i haven't found any string operations with behavior close to such as find() or substr() in istreams.
Here is an example istream content:
delim_oneFUUBARdelim_two
and my goal is to get FUUBAR into a string with as little workarounds as possible.
My current solution was to copy all istream content into a string using this solution for it and then extracting using string operations. Is there a way to avoid this unnecessary copying and only read as much from the istream as needed to preserve all content after the delimited string in case there are more to be found in similar fashion?
You can easily create a type that will consume the expected separator or delimiter:
struct Text
{
std::string t_;
};
std::istream& operator>>(std::istream& is, Text& t)
{
is >> std::skipws;
for (char c: t.t_)
{
if (is.peek() != c)
{
is.setstate(std::ios::failbit);
break;
}
is.get(); // throw away known-matching char
}
return is;
}
See it in action on ideone
This suffices when the previous stream extraction naturally stops without consuming the delimiter (e.g. an int extraction followed by a delimiter that doesn't start with a digit), which will typically be the case unless the previous extraction is of a std::string. Single-character delimiters can be specified to getline, but say your delimiter is "</block>" and the stream contains "<black>metalic</black></block>42" - you'd want something to extract "<black>metallic</black>" into a string, throw away the "</block>" delimiter, and leave the "42" on the stream:
struct Until_Delim {
Until_Delim(std::string& s, std::string delim) : s_(s), delim_(delim) { }
std::string& s_;
std::string delim_;
};
std::istream& operator>>(std::istream& is, const Until_Delim& ud)
{
std::istream::sentry sentry(is);
size_t in_delim = 0;
for (char c = is.get(); is; c = is.get())
{
if (c == ud.delim_[in_delim])
{
if (++in_delim == ud.delim_.size())
break;
continue;
}
if (in_delim) // was part-way into delimiter match...
{
ud.s_.append(ud.delim_, 0, in_delim);
in_delim = 0;
}
ud.s_ += c;
}
// may need to trim trailing whitespace...
if (is.flags() & std::ios_base::skipws)
while (!ud.s_.empty() && std::isspace(ud.s_.back()))
ud.s_.pop_back();
return is;
}
This can then be used as in:
string a_string;
if (some_stream >> Until_Delim(a_string, "</block>") >> whatevers_after)
...
This notation might seem a bit hackish, but there's precedent in Standard Library's std::quoted().
You can see the code running here.
Standard streams are equipped with locales that can do classification, namely the std::ctype<> facet. We can use this facet to ignore() characters in a stream while a certain classification is not present in the next available character. Here's a working example:
#include <iostream>
#include <sstream>
using mask = std::ctype_base::mask;
template<mask m>
void scan_classification(std::istream& is)
{
auto& ctype = std::use_facet<std::ctype<char>>(is.getloc());
while (is.peek() != std::char_traits<char>::eof() && !ctype.is(m, is.peek()))
is.ignore();
}
int main()
{
std::istringstream iss("some_string_delimiter3.1415another_string");
double d;
scan_classification<std::ctype_base::digit>(iss);
if (iss >> d)
std::cout << std::to_string(d); // "3.1415"
}

overloading >> operator for user defined string class

Question
The problem is that i am trying to get user input using insertion operator and initialising the value thechars, to allocate the size to thechars i need the length of input, how do i get it?? And initialise in insertion operator.
Main problem is with insertion operator.
When i run the program it shows the segmentation fault,
plz help
class string1
{
private:
int len;
char *thechars;
//friend ostream& operator<<(ostream&,string1&);##
//friend istream& operator>>(istream&,string1&);##
public:
//string1() :len(0),thechars(NULL){}
string1()
{
thechars = new char[1];
thechars[0] = '\0';
len=0;
// cout << "\tDefault string constructor\n";
// ConstructorCount++;
}
};
// this is the insertion operator i use
istream& operator>>(istream& in, string1& tpr)
{
in >> tpr.thechars;
//tpr.thechars[i+1]='\0';
return in;
}
//this one is the extraction operator
ostream& operator<<(ostream& out,string1& prt)
{
for(int i=0;i<prt.len;i++)
out<<prt.thechars[i];
return out;
}
// main function##
string1 str;
cout << "enter first string" << endl;
cin >> str;
cout << str << endl;
If in is a file input stream, you can do the following:
in.seekg(0, ios::end);
length = in.tellg();
in.seekg(0, ios::beg);
The other option is reading the input stream char by char and double the size of thechars each time it's exhausted. First, introduce one more variable to store the currently allocated size of the buffer --- allocSize. After that update the constructor and operator<< as follows.
Constructor:
string1()
{
allocSize = 1; // initially allocated size
thechars = new char[allocSize];
thechars[0] = '\0';
len=0;
}
Input operator:
istream& operator>>(istream& in, string1& tpr)
{
char inp;
while (in.get(inp)) {
// end-of-input delimiter (change according to your needs)
if (inp == ' ')
break;
// if buffer is exhausted, reallocate it twice as large
if (tpr.len == tpr.allocSize - 1) {
tpr.allocSize *= 2;
char *newchars = new char[tpr.allocSize];
strcpy(newchars, tpr.thechars);
delete[] tpr.thechars;
tpr.thechars = newchars;
}
// store input char
tpr.thechars[tpr.len++] = inp;
tpr.thechars[tpr.len] = '\0';
}
}
But the best option is to use std::string as a type for thechars. Do you really need all this manual memory handling?
Instead of giving the in a char* give it a regular string. Then you can extract the data yourself.
istream& operator>>(istream& in, string1& tpr)
{
string temp;
in >> temp;
tpr.len = temp.length + 1;
tpr.thechars = new char[tpr.len];
tpr.thechars[temp.length] = '\0';
strcpy(tpr.thechars, &temp[0], tpr.len);
return in;
}
you wrote
in>> tpr.thechars; // where thechars="\0";
You allocated only one byte, but i guess you are input string with more bytes.
I think error here.

Is there a getline(istream, string, delim) that appends to string?

This would be great to avoid copying. Is there anything in std or boost to do this?
std::string mystring = "HELLO "; //initial string
int len = mystring.size(); //get initial length
mystring.resize(100); //resize so it's big enough
char* buffer = &mystring[len-1]+1; //get pointer to "spare" space
std::cin.get(buffer , 100-len, '\n'); //read into buffer, until newline
mystring.resize(len + std::cin.gcount()); //shrink to correct size
Since there were no existing solutions, this is what I came up with:
istream& appendline(istream& is, string& str, char delim = '\n')
{
size_t size = str.size();
size_t capacity = str.capacity();
streamsize spaceRemaining = capacity - size;
if (spaceRemaining == 0)
{
capacity = max(static_cast<size_t>(8), capacity * 2);
spaceRemaining = capacity - size;
}
// give getline access to all of capacity
str.resize(capacity);
// get until delim or spaceRemaining is filled
is.getline(&str[size], spaceRemaining, delim);
// gcount includes the delimiter but not the null terminator
size_t newSize = size + is.gcount();
// is failbit set?
if (!is)
{
// if string ran out of space, expand and retry
if (is.gcount()+1 == spaceRemaining)
{
is.clear();
str.resize(newSize);
str.reserve(capacity * 2);
return appendline(is, str, delim);
}
}
else if (!is.eof())
--newSize;
// resize string to fit its contents
str.resize(newSize);
return is;
}
You can use this member function instead (doc):
istream& getline (char* s, streamsize n, char delim );
to read data, and then append that data to a string.
For example, you can wrap this functionality in your own defined function as:
std::istream & getline(std::istream &in, std::string & str, char delim)
{
char buf[1024];
in.getline(buf, 1024, delim);
str.append(buf, in.gcount());
return in;
}
std::string s = "initial string";
getline(std::cin, s, '\n'); //should append to s
Just use the global std::getline instead of the member method
stringstream s;
s << "line1\nline2";
string str;
while(std::getline(s, str)) cout << str;
output: line1line2