Phineas
Phineas

Reputation: 319

read a file to struct with multiple member types

I have a struct:

struct foo {
    int a;
    string b;
    bool c;
    //... ad infinitum
};

I want to load it with data in a .txt file, something like this:

string a, b, c;
string* ptr[] = { &a, &b, &c };
ifstream file("input.txt");
size_t i = 0;
while (file >> *ptr[i])
    (i < 3) ? i++ : i = 0;
//convert a, c to int and bool, then assign

But then I would have to manually convert them from string to int or bool types, is there anyway to load all of them without the need to convert afterward (using void* ptr[] perhaps)?
I don't want like this:

while (!file.eof()){
  file>>a;
  file>>b;
  file>>c;
}

Because Why is iostream::eof inside a loop condition (i.e. `while (!stream.eof())`) considered wrong?, and I don't want to file>> 10 times.

I also haven't learned things like lexical_cast or operator overload so any answer related to them is very helpful, but may not solve my problem.

Here's the txt file:

19127519
Jame Howard
0
19124567
Jacky Thomas
1
14527890
Lucas
1

Upvotes: 1

Views: 166

Answers (3)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136515

You can use BOOST_HANA_DEFINE_STRUCT for compile time reflection for your structure members.

Working example:

#include <iostream>
#include <fstream>
#include <vector>
#include <string>

#include <boost/hana.hpp>
namespace hana = boost::hana;

struct Name {
    std::string value;
};

inline std::istream& operator>>(std::istream& is, Name& name) {
    is.ignore(std::numeric_limits<std::streamsize>::max(), '\n');
    return getline(is, name.value);
}

inline std::ostream& operator<<(std::ostream& os, Name const& name) {
    return os << name.value;
}

template<class T>
std::istream& load(std::istream& is, T& object) {
    hana::for_each(hana::accessors<T>(), [&](auto accessor) {
        is >> hana::second(accessor)(object);
    });
    return is;
};

template<class T>
std::ostream& save(std::ostream& os, T const& object) {
    hana::for_each(hana::accessors<T>(), [&](auto accessor) {
        os << hana::second(accessor)(object) << '\n';
    });
    return os;
};

struct Foo {
    BOOST_HANA_DEFINE_STRUCT(
        Foo,
        (int, a),
        (Name, b),
        (bool, c)
        );
};

int main() {
    std::ifstream is("input.txt");
    std::vector<Foo> foos;

    for(Foo t; load(is, t);)
        foos.push_back(t);

    for(auto const& t : foos)
        save(std::cout, t);
}

Upvotes: 0

bruno
bruno

Reputation: 32596

There are several problems in your solution

  • you read only std::string when you want int and bool
  • your way to read a std::string consider any space as a separator, that means "Jame Howard" is not read into one string but two separated, this is not what your code suppose in the management of the index
  • you (wrongly) save only the last triplet and lost the other

When you open a file always check you was able to do.

You can do that :

#include <fstream>
#include <iostream>
#include <vector>
#include <string>

struct foo {
  int a;
  std::string b;
  bool c;
  //... ad infinitum
};

int main()
{
  std::ifstream file("input.txt");

  if (!file) 
    std::cerr << "cannot open input.txt" << std::endl;
  else {
    std::vector<foo> foos; // to save all struct
    foo f; // to read one struict

    while ((file >> f.a) &&
           file.ignore(std::numeric_limits<std::streamsize>::max(), '\n') && // flush rest of line
           std::getline(file, f.b) &&
           (file >> f.c))
      foos.push_back(f);

    // check
    for (auto f : foos)
      std::cout << f.a << ' ' << f.b << ' ' << f.c << std::endl;
  }
}

The string can contain spaces so it is not possible to do file >> f.b where f is a foo, getline can be used, but because each element is in a separated line it is necessary to flush the end of line after reading the int.

Compilation and execution :

pi@raspberrypi:~ $ g++ -g -Wall f.cc
pi@raspberrypi:~ $ cat input.txt 
19127519
Jame Howard
0
19124567
Jacky Thomas
1
14527890
Lucas
1
pi@raspberrypi:~ $ ./a.out
19127519 Jame Howard 0
19124567 Jacky Thomas 1
14527890 Lucas 1
pi@raspberrypi:~ $ 

Rather than to put the code to read a foo in main it is more natural to define the operator >> (and if needed operator <<), so :

#include <fstream>
#include <iostream>
#include <vector>
#include <string>
#include <limits>

struct foo {
  int a;
  std::string b;
  bool c;
  //... ad infinitum
};

std::istream& operator >>(std::istream & in, foo & f) {
  if ((in >> f.a) &&
      in.ignore(std::numeric_limits<std::streamsize>::max(), '\n') && // flush rest of line
      std::getline(in, f.b))
    in >> f.c;
  return in;
}

int main()
{
  std::ifstream file("input.txt");

  if (!file) 
    std::cerr << "cannot open input.txt" << std::endl;
  else {
    std::vector<foo> foos;
    foo f;

    while (file >> f)
      foos.push_back(f);

    for (auto f : foos)
      std::cout << f.a << ' ' << f.b << ' ' << f.c << std::endl;
  }
}

If foo has private attribute(s) operator << has to be declared friend in foo

Upvotes: 2

Kebberling
Kebberling

Reputation: 102

If I understood your question correctly, you want to do something like the following (pseudo code):

foreach member m of foo
{
    file >> m;
}

because you want to avoid filling each member manually.

If this is what you intended, I don't think there is a simple solution for that. I don't know the rest of your program so I can't say if this is compatible with your project but I would recommend changing the design of your struct. In the way you are currently using it you can't really iterate over the members. You should consider changing it to a single, small struct:

struct foo {
    int a;
    std::string b;
    bool c;
    // no other members here!
}

and a std::vector<foo> containing many of them. In this way you could fill them iteratively in the way bruno answered. This of course only works if there is a repeating pattern in the members (like int, string, bool, int, string, bool, int, string, bool, ... in this case).

As I already said, I am not sure if I understood your question correctly and if your program allows for this design, but I hope I still helped you.

Edit: Maybe you should just look for some sort of serialization (like JSON). This does exactly what you want and there are many libraries out there doing that for you.

Upvotes: 0

Related Questions