Removing repetitive lines of variable assigment - c++

I am currently making a project where in a class I will be taking in a large amount of variables from a user input. Is there a way to stop something like this:
class Person
{
std::string firstName,
lastName,
DoB,
address;
int personID,
durationMins,
totalVisits;
void setValues(std::string values[])
{
firstName = values[0];
lastname = values[1];
DoB = values[2];
// ... etc
}
};
I would like to avoid having a mass of lines dedicated to variable assignment, though I do not know if this is even possible. Any help would be appreciated, thank you.

That can be solved with a simple variadic template function:
template <typename T, typename ...P> void AssignArrayElementsTo(const T *array, P &... objects)
{
std::size_t i = 0;
((objects = array[i++]) , ...);
}
// ...
void setValues(std::string values[])
{
AssignArrayElementsTo(values, firstName, lastName, DoB, address);
}
Unfortunately C++ doesn't have reflection (yet), so you still need to manually list all needed class members. There is no way around that.

In C++11 and over, you can use std::reference_wrapper or the helper function std::ref to generate the reference array of member strings and then the range-based-for of this array is applicable to the initialization as follows:
DEMO
void setValues(std::string values[])
{
std::size_t i = 0;
for(auto& str : {std::ref(firstName), std::ref(lastName), std::ref(DoB), std::ref(address)}){
str.get() = std::move(values[i++]);
}
}
Addition
As #HolyBlackCat suggested in comments, this looks more simple:
DEMO
void setValues(std::string values[])
{
std::size_t i = 0;
for(auto* str : {&firstName, &lastName, &DoB, &address}){
*str = std::move(values[i++]);
}
}

Since you are taking input from the user presumably via some kind of std::istream, an elegant way to take input would be to overload the insertion operator.
class Person {
/* ... */
friend std::istream& operator>>(std::istream& stream, Person& p) {
stream >> p.firstName >> p.lastName >> p.DOB;
/* might not be very straightforward to input address as you need to take care of spaces */
/* put address processing logic here */
std::getline(stream, p.address);
return stream;
}
};
int main () {
Person person;
std::cin >> person;
return 0;
}
You can also overload the extraction operator.

Related

Error C2664: Cannot convert argument from initializer list to std:initializer list

I have a function read() that reads a file and store information into a map. However, whenever the function calls map.insert(), it gives me an error.
Employee and Volunteer are two customs classes with only a few variables.
For example, If I call
ifstream fin;
std::map<std::string, Employee*> employees;
fin.open("Employee.txt");
read<Employee, EMPLOYEE_SIZE>(fin, employees);
It gives the following error:
std::_Tree<std::_Tmap_traits<_Kty,_Ty,_Pr,_Alloc,false>>::insert(std::initializer_list<std::pair<const std::string,Employee *>>)': cannot convert argument 1 from 'initializer list' to 'std::initializer_list<std::pair<const std::string,Employee *>>'`.
Here is the function(Both Employee and Volunteer has the same base class:)
template <typename T, int T_LINE_SIZE>
inline void read(std::ifstream& in, std::map<std::string, T*>& input) {
std::string name = "";
std::string ID="";
std::string line;
double salary;
while (getline(in,line)) {
if (typeid(T) == typeid(Volunteer)) {
name = line.substr(0, 19);
ID = line.substr(20, T_LINE_SIZE);
input.insert({name,new Volunteer(ID,name)}); //error happens here
}else if (typeid(T) == typeid(Employee)) {
name = line.substr(0, 19);
ID = line.substr(20, 29);
salary = std::stod(line.substr(30, T_LINE_SIZE));
input.insert({ name,new Employee(ID, name, salary) }); //error happens here
}
}
}
What am I doing wrong here?
Try following code:
template <typename T, int T_LINE_SIZE>
inline void read(std::ifstream& in, std::map<std::string, T*>& input) {
std::string name = "";
std::string ID="";
std::string line;
double salary;
while (getline(in,line)) {
if constexpr (std::is_same<T, Volunteer>::value) {
name = line.substr(0, 19);
ID = line.substr(20, T_LINE_SIZE);
input.insert({name,new Volunteer(ID,name)});
}else if constexpr (std::is_same<T, Employee>::value) {
name = line.substr(0, 19);
ID = line.substr(20, 29);
salary = std::stod(line.substr(30, T_LINE_SIZE));
input.insert({ name,new Employee(ID, name, salary) });
}
}
}
The reason is that you are using templates. Based on the type of T when you call read() function, type of T is incorrect in scope of one if condition for calling insert() method. This happens because all possible outcomes of code need to be checked at compile time for different types. But when you use if constexpr, that part of code is ignored at compile time, so incorrect part of code will not be seen during compiling code and your code compiles correctly.
#Afshin has shown one way you could do this, but I'd suggest that it's probably not the best way to do the job, at least as a rule.
By distributing the logic a bit differently, you can end up with code that I consider quite a bit simpler.
I would start by overloading operator>> to read a an object of each class you need to store:
struct Volunteer {
std::string name;
std::string ID;
friend std::istream &operator>>(std::istream &is, Volunteer &v) {
std::string line;
std::getline(is, line);
v.name = line.substr(0, 19);
v.ID = line.substr(20); // captures the rest of the line
return is;
}
};
struct Employee {
std::string name;
std::string ID;
double salary;
friend std::istream &operator>>(std::istream &is, Employee &e) {
std::string line;
std::getline(is, line);
e.name = line.substr(0, 19);
e.ID = line.substr(20, 29);
e.salary = std::stod(line.substr(30);
return is;
}
};
I may have omitted other (possibly important) things from Volunteer and Employee--I've only included enough to read one from a file, and store the data we read.
With those in place, reading them from a file becomes quite a bit simpler:
template <class T>
inline void read(std::istream &is, std::map<std::string, T *> &input) {
T t;
while (is >> t)
input.emplace(t.name, new T(t));
}
In this design, Employees and Volunteers each know how to read their own data from a file, and the code that reads the records doesn't need to know the details of how each is read. This makes the code rather more extensible. For example, let's consider what happens when we add a further class for Intern. In your original code (as repaired by Afshin), you'd add a third leg to the if/else if chain:
else if constexpr (std::is_same<T, Intern>::value) {
// code to read and insert data for an Intern here
}
By contrast, with the design I'd advocate, all the code to deal with Interns would be in (or at least associated with) the Intern class itself:
struct Intern {
std::string name;
std::string ID;
bool paid;
double salary;
friend std::istream &operator>>(std::istream &is, Intern &i) {
// code to read data for an Intern here
}
};
...So in this case, the code to read data from a file doesn't require any changes to work with a file of records for Interns instead of Employees or Volunteers.

Why do I get this error "expression must be an lvalue or a function designator" for some of my data members?

I am trying to create a function that prints whatever I feed it from the class I created. I can only get some of my class object data members to work with my print function.
I have tried using pointers and references in some source codes I found online but they didn't work. After working on this problem for about a week now I feel like I have tried everything.
void print(string* object);
enum Degree { SECURITY = 1, NETWORKING = 2, SOFTWARE = 3};
class Student {
public:
Student(string ID, string first, string last, string Email, int Age, int days1, int days2, int days3, Degree StudentDegree) {
studentID = ID;
firstName = first;
lastName = last;
email = Email;
age = Age;
daysTillComplete[0] = { days1 };
daysTillComplete[1] = { days2 };
daysTillComplete[2] = { days3 };
studentDegree = StudentDegree;
return;
}
string getID() const;
void setID(string ID);
string getFirst() const;
void setFirst(string ID);
string getLast() const;
void setLast(string ID);
string getEmail() const;
void setEmail(string ID);
int getAge() const;
void setAge( int Age);
int getdays1();
int getdays2();
int getdays3();
void setDaysTilComplete(int days, int days1, int days2);
Degree getDegree() const;
void setDegree(Degree degreeType);
private:
string studentID = "No name";
string firstName = "No name";
string lastName = "No name";
string email = "No name";
int age = 7;
int daysTillComplete[3];
Degree studentDegree = NETWORKING;
};
void print(string* object) {
cout << *object << endl;
}
string Student::getID() const {
return studentID;
}
string Student::getFirst() const {
return firstName;
}
string Student::getLast() const {
return lastName;
}
string Student::getEmail() const {
return email;
}
int Student::getAge() const {
return age;
}
int Student::getdays1() {
return daysTillComplete[0];
}
int Student::getdays2() {
return daysTillComplete[1];
}
int Student::getdays3() {
return daysTillComplete[2];
}
Degree Student::getDegree() const {
return studentDegree;
}
void Student::setID(string ID) {
studentID = ID;
}
void Student::setFirst(string first) {
firstName = first;
}
void Student::setLast(string last) {
lastName = last;
}
void Student::setEmail(string Email) {
email = Email;
}
void Student::setAge(int studentAge) {
age = studentAge;
}
void Student::setDaysTilComplete(int days1, int days2, int days3) {
daysTillComplete[0] = days1;
daysTillComplete[1] = days2;
daysTillComplete[2] = days3;
}
void Student::setDegree(Degree degreeType) {
studentDegree = degreeType;
}
int main() {
Student vallery("A1", "Vallery", "Williams", "vallery.a.williams1234#gmail.com",21,52,28,32,NETWORKING);
print(&vallery.getID());
print(&vallery.getFirst());
print(&vallery.getLast());
print(&vallery.getEmail());
//print(&vallery.getAge()); <--- will not compile if this statement is in the program
//print(&vallery.getdays1()); <--- will not compile if this statement is in the program
////print(&vallery.getdays2()); <--- will not compile if this statement is in the program
////print(&vallery.getdays3()); <--- will not compile if this statement is in the program
//print(&vallery.getDegree()); <--- will not compile if this statement is in the program
cin.get();
return 0;
}
My expected results for example print(vallery.getFirst); would be print vallery. That works, but when I do print(vallery.getAge); it should be 21 but I can't even compile it because I get the "expression must be an l-value or function designator" error. Also using int instead of string for the data type of the print function doesn't work either. I must be using something incorrectly. Can someone point me in the right direction? After a bunch of research I haven't found any solutions.
There are several things are are probably going wrong with this code.
The most obvious is that your print function expects a string*. The calls that fail are those where you try and give the function something that is not a string*. For example, getAge() returns a int, where you are taking the address of the returned int. So you are trying to call print(int*) which then is failing. To solve this you can either using templating or have overloads. Here is a recommended template function:
template <typename T>
print (const T& _object)
{
cout << _object << endl;
}
A second problem, which is what is masking this first error comes from the fact you are taking a ptr instead of a reference (or a const reference as in my template function). If you want to print something, you want it to definitely be there and not a nullptr. So use references. In saying this, I think the ones that you say will work, definitely shouldn't work (even though they are string*'s) under any sane compiler. Perhaps the current error is hiding those errors.
The error “expression must be an lvalue or a function designator” comes from the fact that you are trying to take the address of an r-value. That is a value that is temporary and/or has not been bound to a name. Your getters return a copy of the value stored in your student object and this value has no address. You are trying to get the address of an addressless value, hence the error that it must be lvalue.
To solve this: Firstly don't use pointers, use a const reference. Secondly, have your getters return a const reference for objects. For example,
const string& Student::getID() const;
If your print function takes a const reference, it is able to handle a temporary variables for the cases where you return an int or other primitive type. Since your other getters return a reference to an addressable value, the print function can also take this reference without having to needlessly copy the values.

Creating a bunch of objects in C++

I have a quite simple situation I don't know how to resolve :
I am parsing a file with addresses (city, street and street number), and have the following class :
class Address
{
std::string city;
std::string street;
std::string number;
};
I would like to create an object for each address I find in the file, but I can't know how many there are since the file can change. Is there a way to create an Array of objects ; or any more suitable solution ?
NOTE : Parser works fine, all there is to do is to set the values in the objects.
You can use std::vector for such purpose: http://en.cppreference.com/w/cpp/container/vector
#include <vector>
struct Address
{
std::string city;
std::string street;
std::string number;
};
bool parseAddress(Address& address)
{
//TODO: implement
//TODO: return "true" if another address has been successfully parsed
}
int main()
{
std::vector<Address> addresses;
Address current;
while(parseAddress(current))
{
addresses.push_back(current);
}
return 0;
}
Like #Serge but rather than use the parser directly define an input operator.
struct Address
{
std::string city;
std::string street;
std::string number;
friend std::istream& operator>>(std::istream& in, Address& address) {
return parseAddress(in, address);
}
};
std::istream& parseAddress(std::istream& in, Address& address)
{
//TODO: implement
//TODO: return stream.
// If the parse failed.
// Then set the bad bit on the stream.
}
int main()
{
std::ifstream file("address.txt");
std::vector<Address> addresses(std::istream_iterator<Address>(file),
std::istream_iterator<Address>());
}

Using Metadata/Inheritance to factor out code across multiple classes

I have two classes that will represent two very simple databases, and each has a "Save" function which will write what's in the class to a file. Since the code within the "Save" function is very similar, I was wondering if I could factor it out.
One of my colleagues said this might be possible with inheritance and/or metadata, so I tried looking into it myself with Google. However, I couldn't find anything that was helpful and am still unsure if what I want to do is even possible.
If it's possible to factor out, then I think I'd need to have another class or function know about each class's types and iterate through them somehow (metadata?). It would check the type of every data, and depending on what the type is, it would make sure that it's correctly output to the text file.
(I know data like name, age, etc. should be private, but to keep this simple I just had everything be public)
class A
{
public:
A() : name(""), age(0) {};
void Save(void)
{
std::string filename = "A.txt";
std::string data;
data += name + "\n";
data += std::to_string(age) + "\n";
std::ofstream outfile(filename);
outfile.write(data.c_str(), data.size());
outfile.close();
}
std::string name;
int age;
};
class B
{
public:
B() : ID(0), points(0) {};
void Save(void)
{
std::string filename = "B.txt";
std::string data;
data += std::to_string(ID) + "\n";
data += std::to_string(points) + "\n";
std::ofstream outfile(filename);
outfile.write(data.c_str(), data.size());
outfile.close();
}
int ID;
int points;
};
int main(void)
{
A a;
B b;
a.name = "Bob"; a.age = 20;
b.ID = 4; b.points = 95;
a.Save();
b.Save();
return 0;
}
A possible solution could be to use metaprogramming (not sure what you mean by metadata), i.e. templates to reuse the common parts
template<typename T1, typename T2>
void TSave(const std::string fname, const T1& p1, const T2& p2) {
std::string filename = fname;
std::stringstream data;
data << p1 << "\n";
data << p2 << "\n";
std::ofstream outfile(filename);
outfile.write(data.str().c_str(), data.str().size());
outfile.close();
}
class A {
...
void Save(void) {
TSave("A.txt", name, age);
}
std::string name;
int age;
};
class B {
...
void Save(void) {
TSave("B.txt", ID, points);
}
int ID;
int points;
};
Live Example
What you are looking for is serialization: saving objects to a file (and one day or another, restore the objects).
Of course, you could write your own serialization framework, and Marco's answer is an interesting start in that direction. But alternatively, you could consider existing libraries, such as boost::serialization :
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
class A {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & name;
ar & age;
}
...
};
class B {
private:
friend class boost::serialization::access;
template<class Archive>
void serialize(Archive & ar, const unsigned int version)
{
ar & ID;
ar & points;
}
...
};
main() {
A a;
B b;
...
{
std::ofstream ofs("myfile");
boost::archive::text_oarchive arch(ofs);
arch << a << b;
}
}
As you see, it's still needed to say what's to be written to the file. However, the code is simplified : you don't have to worry about file management and transformation of data. And it works also with standard containers.
You won't find a C++ trick that automatically determines for a class what's to be saved. Two reasons for that:
C++ allows metaprogramming, but it is not reflexive: there are no standard process to find out at execution time which members compose a class.
In an object, some data can be transient, i.e. it means only something at the time of the execution and depends on the context. For example pointers: you could save the value of a pointer to a file, but it will mean nothing when you reload it later (the pointer is only valid until you free the object). The proper way would be to save the object that is pointed to (but where, when, how?).

Minimizing repetitive code in C++, a not so straightforward case

I am working on a piece of code that copies a 'Person' object from one data representation to another. The names in each class (name, address, title) match, and all types are strings. For each field I want to apply the same transformation, based on some condition that also depends on the field name. The tricky part is that the repeating code uses function suffixes that are based on the field name. It looks something like this:
LibraryA::Person person1;
LibraryB::Person person2;
if (person1.name_valid() && [...somestuff...]) {
string *v = SomeOtherFunction(person.name())
person2.set_name(v);
}
if (person1.address_valid() && [...somestuff...]) {
string *v = SomeOtherFunction(person.address())
person2.set_address(v);
}
if (person1.title_valid() && [...somestuff...]) {
string *v = SomeOtherFunction(person.title())
person2.set_title(v);
}
Is there a trick (or technique :) ) to factor out the repetitive part to a template? I'd prefer a solution that does not involve defining a macro (that would be too easy :) )
This fits your requirements, but whether I would use it or not is a different question. Only if there is a huge amount of repetition I would go through this path, and then I would combine it with a macro to simplify the calling code:
void test_and_set( Person const & person1, Person & person2,
bool (Person::*test)() const,
std::string (Person::*get)() const,
void (Person::*set)( std::string const &) )
{
if ( (person1.*test)() ) {
(person2.*set)( (person1.*get)() );
}
}
Used as:
test_and_set( person1, person2, &Person::valid_name, &Person::get_name, &Person::set_name );
And combined with a local macro:
#define TEST_AND_SET( p1, p2, field ) \
test_and_set( (p1), (p2), &Person::valid_##field, &Person::get_##field, &Person::set_##field )
TEST_AND_SET( person1, person2, name );
TEST_AND_SET( person1, person2, title );
#undef TEST_AND_SET
You can use pointer to member function. For example (I didn't check if this code compiles):
typedef bool (LibraryA::Person::Validator)();
typedef string (LibraryA::Person::FieldGetter)();
typedef void (LibraryB::Person::FieldSetter)(string*);
void ApplyField(LibraryA::Person& person1, LibraryB::Person& person2, Validator vl, FieldGetter get, FieldSetter set)
{
if (person1.vl() && [...somestuff...])
{
string* v = SomeOtherFunction(person1.get());
person2.set(v);
}
}
ApplyField(person1, person2, &LibraryA::Person::name_valid, &LibraryA::Person::name, &LibraryB::Person::set_name);
ApplyField(person1, person2, &LibraryA::Person::address_valid, &LibraryA::Person::address, &LibraryB::Person::set_address);
ApplyField(person1, person2, &LibraryA::Person::title_valid, &LibraryA::Person::title, &LibraryB::Person::set_title);
I don't think templates fit here because all fields are of the same type.
And I don't really know what you have against macros in this case. You can use a macrot to generate the call to ApplyField() if you want.
You can use an array of pointer-to-member objects, fill it with the source and destination of the transformation and then apply the transformation to each entry in that array. This could look something like this:
struct trans_info {
trans_info(bool (S::*valid)() const,
std::string* (S::*get)()const,
void (T::*set)(std::string*)):
valid_(valid),
get_(get),
set_(set)
{
}
bool (S::*valid_)() const;
std::string* (S::*get_)() const;
void (S::*set_)(std::string*);
};
trans_info const info[] = {
trans_info(&S::name_valid, &S::name, &T::set_name),
trans_info(&S::address_valid, &S::address, &T::set_address),
trans_info(&S::title_valid, &S::title, &T::set_title),
...
};
template <typename T, int Size> T* begin(T (&array)[Size]) { return array; }
template <typename T, int Size> T* end(T (&array)[Size]) { return array + Size; }
transform(S const& person1, T& person2)
{
for (trans_info const* it(begin(info)), e(end(info)); it != end; ++it)
{
if ((person1.*(it->valid_))() && [...somestuff...]) {
string *v = SomeOtherFunction(person1.*(it->get_))())
(person2.*(it->set))(v);
}
}
}
Did this quick, certainly not valid C++ but I hope you get the idea:
struct MyFunctor
{
Person *person1, *person2;
void operator()(void Person::*validator(), string* Person::*getter(), void Person::*setter(string *))
{
if (person1->*validator() && [...somestuff...])
{
string* v = SomeOtherFunction(person1->*getter());
person2->*setter(v);
}
}
};
// Usage
MyFunctor f = { person1, person2 };
f(&Person::name_valid, &Person::name, &Person::set_name);
f(&Person::address_valid, &Person::address, &Person::set_address);
f(&Person::title_valid, &Person::title, &Person::set_title);
You can use pointer-to-member arguments to a template.
But I question the use of pointers to strings, that looks like a probable memory leak.
If the person objects are immutable to you, you are out of luck.
If they are not, factor the information out of the method name by using tag classes Person::Name, Person::Address and so on, and then re-writing *_valid and *_set to use function overloading:
bool Person::is_valid( Name, std::string ) {...)
bool Person::is_valid( Address, std::string ) {...)