How to swap strings? - c++

I am trying to swap strings. For one of my functions of my class, I am passing in two strings and I also created a temp variable. I have been trying to compile my code, but it says "no suitable function for conversion from std::string to const char* exists.
void CdLib::swap(string *s1, string *s2)
{
string temp;
strcpy(temp, *s1);
strcpy(*s1, *s2);
strcpy(*s1, temp);
}
class CdLib
{
public:
int n;
char Cd[N_MAX];
string artist;
string title;
int year;
string genre;
string fan;
string imageURL;
CdLib();
void setFromFile(string fileName);
void print(string label);
void sortByYear();
void sortByArtist();
void sortByTitle(string genres[]);
private:
void swap(int *a, int *b);
void swapStrings(string *s1, string *s2);
};
I'm confused why it is trying to convert between string and char when they should all be string. Thank you.

strcpy() takes char* pointers, not string* pointers. It you are not allocating any memory for strcpy() to copy into.
Rather than using strcpy() at all, a better solution is to use std::string::operator= instead:
void CdLib::swap(string *s1, string *s2)
{
string temp = *s1;
*s1 = *s2;
*s1 = temp;
}
Or better, std::swap():
void CdLib::swap(string *s1, string *s2)
{
std::swap(*s1, *s2);
}

I am trying to swap strings
Why would you need to? Sorting can be accomplished via std::sort and you don't have to worry about how the strings get swapped - that's the beauty of C++, such basic operations are all implemented in the standard library.
std::swap supports pretty much everything, so use that.
Don't pass strings as arguments by value. Pass them by const reference. Return them by value. If a function is intended to modify a string in place, then it should take it by non-const reference (i.e. "just" a reference).
Don't write using namespace std - it's bad practice.
I guess that the CdLib class is some sort of a CD library, but you haven't told us what else your program should do.
If I were to write a sketch of this, I'd start with a structure representing the CD information, comparison functions for the CD that can be used in sorting, a function to print out the CD information, and a way to stream the CD information to/from an ostream/istream:
#include <algorithm>
#include <fstream>
#include <iostream>
#include <string>
#include <vector>
struct CDInfo
{
std::string artist;
std::string title;
std::string genre;
std::string fan;
std::string imageUrl;
int year;
friend void swap(CDInfo& a, CDInfo& b)
{
// see https://stackoverflow.com/a/2684544/1329652 for rationale
using std::swap; // bring in swap for built-in types
swap(a.artist, b.artist);
swap(a.title, b.title);
swap(a.genre, b.genre);
swap(a.fan, b.fan);
swap(a.imageUrl, b.imageUrl);
swap(a.year, b.year);
}
};
bool lessByYear(const CDInfo &l, const CDInfo &r) {
return l.year < r.year;
}
bool lessByArtist(const CDInfo &l, const CDInfo &r) {
return l.artist < r.artist;
}
void print(std::ostream &os, const CDInfo &cd) {
os << "Artist: " << cd.artist
<< "\n Title: " << cd.title
<< "\n Genre: " << cd.genre
<< "\n Fan: " << cd.fan
<< "\n Image: " << cd.imageUrl
<< "\n Year: " << cd.year << "\n";
}
std::istream &operator>>(std::istream &is, CDInfo &cd)
{
std::string year;
std::getline(is, cd.artist);
std::getline(is, cd.title);
std::getline(is, cd.genre);
std::getline(is, cd.fan);
std::getline(is, cd.imageUrl);
if (std::getline(is, year)) cd.year = std::stoi(year);
return is;
}
std::ostream &operator<<(std::ostream &os, const CDInfo &cd)
{
os << cd.artist << '\n' << cd.title << '\n'
<< cd.genre << '\n' << cd.fan << '\n'
<< cd.imageUrl << '\n' << cd.year << '\n';
return os;
}
Then I'd write a class representing the CD library, with methods to access the individual CDs, iterators to access the entire collection, methods using the std::sort algorithm and the comparison functions to sort the library, and methods to load/save it from/to file, and to print the entire library (by default to stdout):
class CDLibrary
{
std::vector<CDInfo> m_CDs;
public:
CDLibrary() = default;
int count() const { return m_CDs.size(); }
void resize(int newCount) { m_CDs.resize(newCount); }
CDInfo &getCD(int index) { return m_CDs[index]; }
const CDInfo &getCD(int index) const { return m_CDs[index]; }
auto begin() { return m_CDs.begin(); }
auto end() { return m_CDs.end(); }
auto begin() const { return m_CDs.begin(); }
auto end() const { return m_CDs.end(); }
auto cbegin() const { return m_CDs.begin(); }
auto cend() const { return m_CDs.end(); }
void sortByYear() {
std::sort(begin(), end(), lessByYear);
}
void sortByArtist() {
std::sort(begin(), end(), lessByArtist);
}
void addCD(const CDInfo &cd) {
m_CDs.push_back(cd);
}
void removeCD(int index) {
m_CDs.erase(m_CDs.begin() + index);
}
bool load(const std::string &filename);
bool save(const std::string &filename) const;
void printAll(std::ostream &os = std::cout) const {
int n = 1;
for (auto &cd : *this) {
os << "--- CD #" << n << '\n';
print(os, cd);
}
}
};
Of course I'd also implement the streaming operators for the entire library, just as we did for the individual CDInfo:
std::istream &operator>>(std::istream &is, CDLibrary &lib) {
std::string count;
if (std::getline(is, count)) {
lib.resize(std::stoi(count));
for (auto &cd : lib)
if (!(is >> cd)) break;
}
return is;
}
std::ostream &operator<<(std::ostream &os, const CDLibrary &lib) {
if (!(os << lib.count() << '\n')) return os;
for (auto &cd : lib)
if (!(os << cd)) break;
return os;
}
Then, the load and save convenience methods can be expressed n terms of those streaming operators:
bool CDLibrary::load(const std::string &filename) {
std::ifstream ifs(filename);
try {
return ifs.good() && ifs >> *this;
} catch (...) {}
return false;
}
bool CDLibrary::save(const std::string &filename) const {
std::ofstream ofs(filename);
return ofs.good() && ofs << *this;
}
Hopefully this gives you some idea how such code might look. I'm not quite sure what you expected to achieve with void sortByTitle(string genres[]), so I didn't implement it. Feel free to comment under this answer to explain, as well as edit the question to make it clear what is the functionality you need.

#include <iostream>
#include<bits/stdc++.h>
using namespace std;
int main() {
string str1 = "Hello";
string str2 = "World";
swap(str1,str2);
cout<<str1<<" ";
cout<<str2;
}
o/p:
Success #stdin #stdout 0s 4492KB
World Hello

Related

Creating a sticky manipulator that inserts a delimiter between each token of an output stream

I've seen examples that allow you to create a manipulator that inserts delimiters but none of those manipulators are sticky. That is, the manipulator returns a special class that inserts the delimiter, rather than modifying the output stream permanently so that it can do it on its own.
I want to be able to do this:
std::cout << sep(", ");
std::cout << "hello" << "world";
// "hello, world"
At the moment this prints "h, e, l, l, o, w, o, r, l, d" when I need it to be "hello, world". The reason I'm getting the wrong output is because I put the printing in the overflow() method and overflow() is being called for each character. I'm not sure where is the appropriate place to put it.
Sorry about it being verbose. If I knew a simpler way to write it I would. Just start from the bottom and work your way up:
#include <iostream>
#include <cstring>
// index for delimiter
int separator() {
static int idx = std::ios_base::xalloc();
return idx;
}
// index for storage of dynamically-allocated buffer
int rdbuffer() {
static int idx = std::ios_base::xalloc();
return idx;
}
struct custom_separator : std::streambuf {
public:
custom_separator(std::ostream& _stream)
: stream(_stream), buffer(_stream.rdbuf()) {}
int_type overflow(int_type c) {
// has a token been read already
if (token_read) {
char* delim = static_cast<char*>(stream.pword(separator()));
// print delim
buffer->sputn(delim, strlen(delim));
}
token_read = true;
return buffer->sputc(c);
}
private:
std::ostream& stream;
std::streambuf* buffer;
bool token_read = false;
};
// deletes the buffer and the delimiter
void cleanup(std::ios_base::event evt, std::ios_base& str, int idx) {
if (evt == std::ios_base::erase_event) {
delete static_cast<const char*>(str.pword(idx));
delete static_cast<custom_separator*>(str.pword(rdbuffer()));
}
}
std::ostream& set_separator(std::ostream& os, const char* str) {
if (!os.bad()) {
// If a separator string doesn't exist, assign os a buffer that prints one
if (!os.pword(separator())) {
auto buf = new custom_separator(os);
os.rdbuf(buf);
// this is to keep track of buf so we can delete it later
os.pword(rdbuffer()) = static_cast<void*>(buf);
os.register_callback(cleanup, separator());
}
// delete the previous separator
delete static_cast<const char*>(os.pword(separator()));
// store the new one
os.pword(separator()) = (void*)(str);
}
return os;
}
struct sep {
explicit sep(const char* _sep)
: separator(new char[strlen(_sep) + 1]) {
strcpy(separator, _sep);
}
sep(const sep&) = delete;
sep(const sep&&) = delete;
char* separator;
};
std::ostream& operator<<(std::ostream& os, const sep& manip) {
set_separator(os, manip.separator);
return os;
}
int main() {
std::cout << sep(", ");
std::cout << "hello";
std::cout << "world";
// "h, e, l, l, o, w, o, r, l, d"
}
The main issue with overflow() is that I don't know when to detect when the end of a token like "hello" has been read to know when to insert.
Try the following. Additionally, new line break processing (new line symbol) has been added so that the separator is not added during the transfer (after new line).
#include <iostream>
class MyStream
{
public:
struct Sep
{
Sep (const std::string& sep_value = ""):
m_sep(sep_value)
{
}
operator std::string()
{
return m_sep;
}
private:
std::string m_sep;
};
MyStream& operator << (const Sep& sep)
{
m_sep = sep;
m_add_sep = false;
return *this;
}
MyStream& operator << (const std::string& str)
{
if(str.find(MyStream::endl) != std::string::npos)
m_add_sep = false;
operator<< <std::string>(str);
m_add_sep = false;
return *this;
}
template <typename T>
MyStream& operator << (const T& value)
{
if(m_add_sep)
std::cout << static_cast<std::string>(m_sep);
std::cout << value;
m_add_sep = true;
return *this;
}
static const std::string endl;
private:
Sep m_sep;
bool m_add_sep = false;
};
const std::string MyStream::endl = std::string("\n");
int main()
{
MyStream stream;
stream << "hello" << "world" << MyStream::endl; // prints "helloworld"
stream << MyStream::Sep(", ");
stream << "hello" << "world" << MyStream::endl; // prints "hello, world"
stream << 1 << 2;
stream << 3 << MyStream::endl; // both lines prints "1, 2, 3"
stream << MyStream::Sep();
stream << 1 << 2 << 3 << MyStream::endl; // prints "123"
return 0;
}
Here's a possible solution. The separator is the same for all streams.
namespace alt_std
{
struct sep
{
sep(const char* s) { s_ = s; }
friend std::ostream& operator<<(std::ostream& os, const sep& sm)
{
return os;
}
inline static std::string s_{};
};
std::ostream& operator<<(std::ostream& os, const char* s)
{
return std::operator<<(os, s), std::operator<<(os, sep::s_);
}
}
int main()
{
using namespace alt_std;
std::cout << sep(" ") << "hello";
std::cout << "world" << std::endl; // "hello world\n"
std::cout << sep("") << "hel" << "lo"; // "hello"
}
If you want to do it at the stream object level, it's more difficult because stream objects don't have a custom storage space where you can store the value of the separator.
Or you could simply wrap the stream object:
template< typename OS >
struct wrapper
{
wrapper(OS& os, const char* sep) : os_{ os }, sep_{ sep } {}
template< typename T>
friend wrapper& operator<<(wrapper& os, const T& v)
{
os.os_ << v << os.sep_;
return os;
}
OS& os_;
std::string sep_;
};
int main()
{
wrapper os{ std::cout, " " };
os << "hello" << "world";
}

Easiest way to load variables from a text file

I have a program where I want to load Variables from a text file to use them as default variables.
The text file should look like this:
Name=No Name
Age=8
Gender=male
etc.
Is there a simpler way and if not how do I do that in the place with the question marks?
My Code look like this:
int Age;
std::string Name;
bool male;
if(f.is_open())
{
while (!f.eof())
{
getline(f, line);
if (line.find("Name=") == std::string::npos)
{
Name=?????;
continue;
}
else if (line.find("Gender=") == std::string::npos)
{
if(????? == "true"); then
male=true;
else
male=false;
continue;
}
else if (line.find("Age=") == std::string::npos)
{
Age=?????;
continue;
}
//etc. ...
}
f.close();
Is there a simpler way?
You could use a serialization library, like cereal or Boost, as #JesperJuhl suggested.
However, I would strongly suggest to take a step back, and review your approach. You are asking for an improvement, but you don't have a good solution at this point, because Why is iostream::eof inside a loop condition considered wrong?
As I had written here, I will use std::getline() as the loop condition instead of ios::eof(), in order to parse the file, line by line.
How do I do that in the place with the question marks?
Then, for every line, I will tokenize it, based on a delimiter (equal sign in your case), in order to extract two tokens, the name of the variable and its default value. Read more about it in Parse (split) a string in C++ using string delimiter (standard C++)
Afterwards, I would use an if-else approach (You could use a switch statement instead) to check the name of the variable, and assign its default value to the actual variables of the program.
Full code example:
#include <iostream>
#include <string>
#include <fstream>
int main(void) {
std::string defaultName, gender;
int age;
std::ifstream infile("mytextfile.txt");
std::string line, varName, defaultValue;
std::string delimiter = "=";
while (std::getline(infile, line)) {
varName = line.substr(0, line.find(delimiter));
defaultValue = line.substr(line.find(delimiter) + 1);
if(varName == "Name") {
defaultName = defaultValue;
continue;
} else if(varName == "Age") {
age = std::stoi(defaultValue);
continue;
} else if(varName == "Gender") {
gender = defaultValue;
continue;
} else {
std::cout << "Unknown entry: " << line << std::endl;
}
}
std::cout << defaultName << ", " << age << ", " << gender << std::endl;
return 0;
}
Output:
No Name, 8, male
If you feel a need to write it yourself instead of using a ready library, you could use a std::unordered_map<> and add some streaming and extraction support around it. Here's an example with comments in the code:
#include <string>
#include <unordered_map>
class KeyValue { // Key Value
std::unordered_map<std::string, std::string> m_kv{};
public:
// at() is used to get a reference to a Value given the supplied Key. It uses
// the function with the same name in the unordered_map.
inline std::string& at(const std::string& Key) { return m_kv.at(Key); }
inline const std::string& at(const std::string& Key) const { return m_kv.at(Key); }
// The "as<T>" function below is used to extract values from the map.
// The exact version of the function that will be used depends on the type
// you want to extract from the string. Explicit specializations of the function
// are declared outside the class.
// A generic conversion function to anything that can be constructed from a std::string
template<typename T>
T as(const std::string& Key) const {
return at(Key);
}
// A function to extract directly into a variable using the proper as<T>
template<typename T>
void extract_to(T& var, const std::string& Key) const {
var = as<T>(Key);
}
// A friend function to read from an input stream (like an open file) and
// populate the unordered_map.
friend std::istream& operator>>(std::istream&, KeyValue&);
};
// Explicit specializations of KeyValue::as<T>()
// floats
template<>
float KeyValue::as(const std::string& Key) const {
return std::stof(at(Key));
}
template<>
double KeyValue::as(const std::string& Key) const {
return std::stod(at(Key));
}
template<>
long double KeyValue::as(const std::string& Key) const {
return std::stold(at(Key));
}
// signed integers
template<>
int KeyValue::as(const std::string& Key) const {
return std::stoi(at(Key));
}
template<>
long KeyValue::as(const std::string& Key) const {
return std::stol(at(Key));
}
template<>
long long KeyValue::as(const std::string& Key) const {
return std::stoll(at(Key));
}
// unsigned integers
template<>
unsigned KeyValue::as(const std::string& Key) const {
return std::stoul(at(Key));
}
template<>
unsigned long KeyValue::as(const std::string& Key) const {
return std::stoul(at(Key));
}
template<>
unsigned long long KeyValue::as(const std::string& Key) const {
return std::stoull(at(Key));
}
// bool
template<>
bool KeyValue::as(const std::string& Key) const {
const std::string& val = at(Key);
if(val=="true" || val=="1") return true;
else if(val=="false" || val=="0") return false;
throw std::range_error("\"" + Key + "\" is neither true nor false");
}
// the friend function that extracts key value strings from a stream
std::istream& operator>>(std::istream& is, KeyValue& kv) {
std::string line;
// read one line at a time
while(std::getline(is, line)) {
auto pos = line.find('=');
if(pos == std::string::npos || pos == 0) {
// if '=' was not found (or found at pos 0), set the failbit on the stream
is.setstate(std::ios::failbit);
} else {
// if '=' was found, put the Key and Value in the map by
// using substr() to split the line where the '=' was found
kv.m_kv.emplace(line.substr(0, pos), line.substr(pos + 1));
}
}
return is;
}
With that in place, you can read a file and populate the variables that you've preferably put in a class / struct. Example:
#include <fstream>
struct Variables {
std::string Name{};
unsigned int Age{};
std::string Gender{};
double PI{};
bool Hungry{};
bool Sad{};
Variables(const std::string& filename) {
std::ifstream is(filename);
if(is) {
KeyValue tmp;
is >> tmp; // stream the whole file into tmp
// extract values
tmp.extract_to(Name, "Name");
tmp.extract_to(Age, "Age");
tmp.extract_to(Gender, "Gender");
tmp.extract_to(PI, "PI");
tmp.extract_to(Hungry, "Hungry");
tmp.extract_to(Sad, "Sad");
} else throw std::runtime_error("Could not read \""+filename+"\".");
}
};
Example data file (vars.dat):
Name=No name
Age=8
Gender=male
PI=3.14159
Hungry=true
Sad=false
...and a main example::
#include <iostream>
int main() {
try {
Variables var("vars.dat"); // open file and populate variables
std::cout << std::boolalpha
<< "Name: " << var.Name << "\n"
<< "Age: " << var.Age << "\n"
<< "Gender: " << var.Gender << "\n"
<< "PI: " << var.PI << "\n"
<< "Hungry: " << var.Hungry << "\n"
<< "Sad: " << var.Sad << "\n";
} catch(const std::exception& ex) {
std::cerr << ex.what() << "\n";
}
}
I tried to simplify the solution of #Ted Lyngmo:
... I think it is not the fastest way and not the best, but it is more simple and more short:
#include <sstream>
class loadVars
{
public:
std::string file;
loadVars() { }
//Input ->
loadVars(std::string Text) {
this->setFile(Text);
}
loadVars(std::istream& is) {
this->setFile(is);
}
friend void operator>>(std::istream& is, loadVars& lv) {
lv.file = std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
}
void setFile(std::string Text) {
this->file = Text;
}
void setFile(std::istream& is) {
this->file = std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
}
//<-
std::string extract_to_first(std::string to) {
std::string line;
std::stringstream s_string = std::stringstream(this->file);
while (std::getline(s_string, line)) {
if(line.find("=") != std::string::npos) {
if(line.substr(0,line.find("=")) == to) {
return line.substr(line.find("=")+1);
}
}
}
return "-1";
}
};
I would not reinvent this. As suggested, libraries for serialization exist. Consider Boost.PropertyTree as an example and Boost can be helpful to learn in general.

Array based on strings, char list conversions

I create a multiple table of string type. I keep variables inside (int, string). It gives me an error:
[Error] cannot convert 'std::string {aka std::basic_string}' to 'char' in assignment
I've created a tree-shaped suite of functions.The program create a multiple array from a file with this format:
11 10 2001
CSKA Moscow
12 1
Bayern Munich
...
Program:
void llegir(std::fstream &_contingut, std::string *_taula) {
//declaro variables
int dia, mes, any, puntsLocal, puntsVisitant, i = 0;
std::string equipLocal, equipVisitant;
while (!(_contingut.eof())) {
//llegeixo arxiu
_contingut >> dia >> mes >> any; //primera linea
_contingut.ignore();
getline(_contingut, equipLocal); //segona linea
_contingut >> puntsLocal >> puntsVisitant; //tercera linea
_contingut.ignore();
getline(_contingut, equipVisitant); //quarta linea
_taula[i][0] = dia;
_taula[i][1] = mes;
_taula[i][2] = any;
_taula[i][3] = equipLocal.c_str();
_taula[i][4] = puntsLocal;
_taula[i][5] = equipVisitant.c_str();
_taula[i][6] = puntsVisitant;
i++;
}
}
void creartaulaDelFitxer(std::string _fitxer, std::string *_taula, int &n_taula) {
std::fstream arxiu;
arxiu.open(_fitxer, std::fstream:: in );
if (arxiu.is_open()) {
std::cout << "existeix";
} else {
std::cout << "ERROR OBERTURA FITXER";
}
llegir(arxiu, _taula);
}
int main(int argc, char** argv) {
std::string fitxer;
std::string eurolliga[300][7];
int n_taula = 0;
std::cout << "INTRODUEIX NOM FITXER:" << std::endl;
std::cin >> fitxer;
creartaulaDelFitxer(fitxer, *eurolliga, int n_taula);
}
You are mixing pointers, chars and strings which will certainly cause a lot of headache. Try to use the standard containers, like std::string and std::vector. If you need many strings, put them in a vector. When you have a collection of data like
11 10 2001
CSKA Moscow
12 1
Bayern Munich
that describes some entity, create a class for it. You can then add streaming operators for that class to read in one of these entities. If you have a collection of entities, make a container and add streaming operators for that too.
Example:
#include <iostream>
#include <fstream>
#include <vector>
class Game {
std::string equipLocal{};
std::string equipVisitant{};
int dia{}, mes{}, any{};
int puntsLocal{}, puntsVisitant{};
public:
friend std::istream& operator>>(std::istream&, Game&);
friend std::ostream& operator<<(std::ostream&, const Game&);
};
// read one entity from an istream
std::istream& operator>>(std::istream& is, Game& g) {
if(is >> g.dia >> g.mes >> g.any) {
is.ignore();
if(std::getline(is, g.equipLocal) && (is >> g.puntsLocal >> g.puntsVisitant)) {
is.ignore();
std::getline(is, g.equipVisitant);
}
}
return is;
}
// write one entity to an ostream
std::ostream& operator<<(std::ostream& os, const Game& g) {
return os << g.dia << " " << g.mes << " " << g.any << "\n"
<< g.equipLocal << "\n"
<< g.puntsLocal << " " << g.puntsVisitant << "\n"
<< g.equipVisitant << "\n";
}
class EuroLiga {
std::vector<Game> games{};
public:
bool Load(const std::string& filename) {
std::ifstream arxiu(filename);
if(arxiu) {
games.clear();
arxiu >> *this; // use this class' friend, operator>>
return true;
} else
return false;
}
// support for basic non-const iteration over the 'games'
std::vector<Game>::iterator begin() { return games.begin(); }
std::vector<Game>::iterator end() { return games.end(); }
friend std::istream& operator>>(std::istream&, EuroLiga&);
};
// read all entities from an istream
std::istream& operator>>(std::istream& is, EuroLiga& el) {
Game tmp;
while(is >> tmp) {
el.games.push_back(std::move(tmp));
}
return is;
}
int main() {
EuroLiga euroliga;
std::string fitxer;
std::cout << "INTRODUEIX NOM FITXER: ";
std::cin >> fitxer;
euroliga.Load(fitxer);
// display all entities read from the file
for(auto& g : euroliga) {
std::cout << g << "\n";
}
}
void llegir(std::fstream &_contingut, std::string *_taula)
Gets a pointer to a string called _taula, this is probably your array.
However you assign something to your array like this:
_taula[i][0] = dia; // allowed, but bad because char is basically a number.
[...]
_taula[i][3] = equipLocal.c_str(); // not allowed, you are assigning a char pointer to a char.
taula[i] is the i-th string in your array. And by putting [0] you assign to the first character in that string. dia is an integer though.
For example
std::string[] = {"Hello", "world", "I", "am", "alive"};
std::cout << string[1] << std::endl; // output "world"
std::cout << string[1][0] << std::endl; // output 'w'
You can not assign a string to a single character.
As a side note, you should look into declaring an enumeration for your array index (and a constant for it's size) to make it more clear and improve maintainability.
What you should probably be doing is create a struct or class for your, whatever it is
struct whateverItIs {
int dia, mes, any, puntsLocal, puntsVisitant;
std::string equipLocal, equipVisitant;
};
Make a new instance of that in your llegir and push it to the back of a std::vector you get by reference.
Just remember to delete() them later especially before that vector goes out of scope.

using iterator in ostream fails

I am attempting to implement a std::list to replace a linked list in this assignment. I am not allowed to change the declarations and can only change code in the .cpp file. For the most part I am making progress but I am having trouble implementing this
std::ostream& operator<< (std::ostream& out, const Section& section);
namely when I try to create an iterator it fails. I've used the iterator elsewhere in the code so I don't understand why it's failing here, I believe it's because it's private but I'm not sure how to resolve the issue without changing the .h file which was explicitly prohibited:
std::ostream& operator<< (std::ostream& out, const Section& section)
{
// 1. print the section header
out << setw(8) << left << section.getCourse()
<< setw(6) << left << section.getCallNumber();
out << ": " << section.getNumberOfStudents() << " students\n";
// 2. collect the students, sort, and print
Student* students = new Student[section.getNumberOfStudents()];
{
int i = 0;
for ( auto pos = section.students.begin();
pos != section.students.end(); pos++)
{
students[i] = pos;
++i;
}
}
sort (students, students+section.getNumberOfStudents());
for (int i = 0; i < section.getNumberOfStudents(); ++i)
out << " " << students[i] << "\n";
out << flush;
return out;
}
students[i] = pos;
should be changed to
students[i] = *pos;
because you want to copy the Student the iterator references, not the iterator itself.
But why a dynamic array of Student rather than a std::vector<Student>? Currently you have a memory leak because you don't delete[] students;
Edit 1
Removed.
Edit 2
Other than that, all I can see that it wrong is a missing std:: in front of
sort (students, students+section.getNumberOfStudents());
this is assuming there is no custom sort method being used.
Edit 3
Going off the rails here:
students[i] = *pos;
copies a Student from the list into the dynamic array students. This could be expensive, so here is an alternative:
First the bits and pieces needed to prove this out: Required includes
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <functional>
a minimal Student class
class Student
{
std::string name;
public:
Student(std::string inname):name(inname)
{
}
const std::string & getname() const
{
return name;
}
friend bool operator<(const Student & a, const Student &b)
{
return a.name < b.name;
}
};
a minimal Section class
class Section
{
public:
std::list<Student> students;
};
a minimal outstream operator
std::ostream& operator<<(std::ostream& out, const Section& section)
{
A std::vector instead of an array, and a vector of constant references so we don't have to copy the students.
std::vector<std::reference_wrapper<const Student>> students;
Store references in the vector. Probably could do a one liner with std::copy and std::back_inserter, but this is getting a bit too much to absorb for one example.
for (const auto & student: section.students)
{
students.push_back(std::ref(student));
}
Sort the vector
std::sort(students.begin(), students.end());
print the vector
for (const auto & student: students)
{
out << student.get().getname() << " ";
}
return out;
}
and one main to rule them all and in the darkness bind them
int main()
{
Section s;
s.students.emplace_front("Tom");
s.students.emplace_front("Dick");
s.students.emplace_front("Harry");
std::cout << s;
}
And all in one easy to cut-n-paste block:
#include <iostream>
#include <list>
#include <vector>
#include <algorithm>
#include <functional>
class Student
{
public:
std::string name; // this is me being lazy. name should be private
Student(std::string inname):name(inname)
{
}
const std::string & getname() const
{
return name;
}
friend bool operator<(const Student & a, const Student &b)
{
return a.name < b.name;
}
};
class Section
{
public:
std::list<Student> students;
};
std::ostream& operator<<(std::ostream& out, const Section& section)
{
std::vector<std::reference_wrapper<const Student>> students;
// store references in the `vector`.
for (const auto & student: section.students)
{
students.push_back(std::ref(student));
}
// Sort the `vector`
std::sort(students.begin(), students.end());
// print the `vector`
for (const auto & student: students)
{
out << student.get().getname() << " ";
}
return out;
}
int main()
{
Section s;
s.students.emplace_front("Tom");
s.students.emplace_front("Dick");
s.students.emplace_front("Harry");
std::cout << s;
}
Or do what Remy suggested and use a std::vector<Student *> and a custom comparator to dereference the pointers for std::sort.
As others have stated, the error is because you are not dereferencing the iterator when populating your students[] array:
students[i] = pos; // <-- should be *pos instead!
I would suggest an alternative approach that should be faster and more efficient:
std::ostream& operator<< (std::ostream& out, const Section& section)
{
// 1. print the section header
out << setw(8) << left << section.getCourse()
<< setw(6) << left << section.getCallNumber();
out << ": " << section.getNumberOfStudents() << " students\n";
// 2. collect the students, sort, and print
std::vector<const Student*> students;
students.reserve(section.getNumberOfStudents());
for ( auto pos = section.students.cbegin();
pos != section.students.cend(); ++pos)
{
students.push_back(&(*pos));
}
sort (students.begin(), students.end(),
[](const Student *a, const Student *b) { return (*a < *b); }
);
for ( auto pos = students.cbegin();
pos != students.cend(); ++pos)
{
out << " " << *(*pos) << "\n";
}
out << flush;
return out;
}
I appreciate all your answers. Ended up being a much more basic issue. I had to implement the Section iterators to return student iterators.
Section::iterator Section::begin() {
return students.begin();
}
Section::const_iterator Section::begin() const {
return students.begin();
}
Section::iterator Section::end() {
return students.begin();
}
Section::const_iterator Section::end() const {
return students.begin();
}

Using push_back for vector in c++

I am having trouble using push_back for vectors in c++.
My vector is named data.
In my loop I want to add 50 to data[i].getQuantity then push_back to data
These are things that I have tried.
data.push_back(data[i].getQuantity());
and
float myFloat = data[i].getQuantity() + 50;
data.push_back(data[i].getQuantity(myFloat));
data.push_back(myFloat);
The error is saying
No function to call to push_back
Here is my code:
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <iterator>
struct Input
{
friend std::istream& operator >>(std::istream& inp, Input& item);
friend std::ostream& operator <<(std::ostream& outp, Input const& item);
std::string group;
std::string total_pay;
float quantity;
// default constructor. sets up zero-elements
Input() : group(), total_pay(), quantity()
{
}
Input(std::string groupIn, std::string total_payIn, float quantityIn) :
group(std::move(groupIn)),
total_pay(total_payIn),
quantity(quantityIn)
{
}
// Accessors
std::string const& getGroup() const { return group; }
std::string getTotalPay() const { return total_pay; }
float getQuantity() const { return quantity; }
};
// global free function for extracting an Input item from an input stream
std::istream& operator >>(std::istream& inp, Input& item)
{
return (inp >> item.group >> item.total_pay >> item.quantity);
}
// global operator for inserting to a stream
std::ostream& operator <<(std::ostream& outp, Input const& item)
{
outp
<< item.getGroup() << ", "
<< item.getTotalPay() << ", "
<< item.getQuantity();
return outp;
}
struct ctype : std::ctype<char>
{
static mask* make_table()
{
static std::vector<mask> table(classic_table(),
classic_table() + table_size);
table[','] |= space;
return &table[0];
}
ctype() : std::ctype<char>(make_table()) { }
};
int main() {
std::fstream infile("employee.dat");
std::vector<Input> data;
std::string line;
try {
while (std::getline(infile, line))
{
std::istringstream iss(line);
Input inp;
iss.imbue(std::locale(iss.getloc(), new ctype));
while (iss >> inp) // calls our extraction operator >>
data.push_back(inp);
if (iss.fail() && !iss.eof())
std::cerr << "Invalid input line: " << line << '\n';
}
// dump all of them to stdout. calls our insertion operator <<
std::copy(data.begin(), data.end(),
std::ostream_iterator<Input>(std::cout,"\n"));
std::ofstream outp("output.dat");
for(int i = 0; i < data[i].getQuantity(); i++)
{
float myFloat = data[i].getQuantity() + 50;
data.push_back(myFloat);
outp << data[i].getGroup() << ',' << data[i].getTotalPay() << ',' << data[i].getQuantity() + 50 << '\n';
}
} catch (std::exception& e) {
std::cout << "There was an error: " << '\n';
return 1;
}
return 0;
}
Your vector is of type std::vector<Input>. That means you can only put objects of type Input into it. You can't push_back a float into such a vector.
If your intention is to create a new Input object and push that back into your vector, you could do something like
data.push_back(Input(data[i].getGroup(), data[i].getTotalPay(), data[i].getQuantity() + 50))
On the other hand, if you are simply trying to modify an element in data without adding a new element to data, you could just do
data[i].quantity += 50;
This works because you use a struct rather than a class. In a struct, variables default privacy level is public. If you wanted to use a class, or you just don't want to directly access the struct members, you would have to create a setter function for quantity.