4

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.

melpomene
  • 84,125
  • 8
  • 85
  • 148
Fin M.
  • 139
  • 10
  • Research loops. – Jesper Juhl Mar 17 '19 at 12:30
  • @JesperJuhl as far as I know you cant make a loop that assigns completely different variables? Loops would not help here, that is the issue I am trying to deal with. – Fin M. Mar 17 '19 at 12:33
  • I'm not aware of such way. You can possibly avoid multiple lines for assignments. See this how to do this with tie: https://stackoverflow.com/questions/13301370/c11-equivalent-of-pythons-x-y-z-array . See this how to convert array to tie: https://stackoverflow.com/questions/40956062/unpacking-a-stdarray . You still have to declare all these variables. –  Mar 17 '19 at 12:33
  • You can make a loop that reads data into subsequent elements of a container (like `std::vector`). – Jesper Juhl Mar 17 '19 at 12:34
  • 2
    You should try overloading the stream operators. – Yashas Mar 17 '19 at 12:34
  • @JesperJuhl I see what you mean now; however, I would like to avoid doing this as it may make the code hard to understand to others as there would be no variable names, just numbers. – Fin M. Mar 17 '19 at 12:40
  • If you overload the `>>` operator for your class, you can directly take input without having to use a `std::string[]` as an intermediate. – Yashas Mar 17 '19 at 12:41

3 Answers3

4

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.

HolyBlackCat
  • 78,603
  • 9
  • 131
  • 207
2

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++]);
    }
}
Hiroki
  • 2,780
  • 3
  • 12
  • 26
  • 1
    Alternatively you can use pointers: `auto *str : {&firstName, &lastName, ...}`. It's a bit less characters to type. – HolyBlackCat Mar 17 '19 at 13:39
0

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.

Yashas
  • 1,154
  • 1
  • 12
  • 34
  • 1
    How do you expect `stream >> ... >> p.address` to work? `address` likely contains spaces. –  Mar 17 '19 at 13:28
  • That was an example and is not supposed to be a drop-in replacement. The user needs to add more code to take inputs containing spaces (whatever logic was used to obtain the address to pass to `setValues` can be put inside the `>>` overload). The point intended to make was if the input is being taken from the user, then it's appropriate to overload the `>>` operator to take all the input. I have edited the answer to make it clear. – Yashas Mar 17 '19 at 13:30