1

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
bruno
  • 32,421
  • 7
  • 25
  • 37
Phineas
  • 159
  • 2
  • 10
  • Why can't you read directly into `foo` members? What is `ptr` for? – Evg Apr 25 '20 at 14:49
  • out of the fact your code cannot works, we cannot indicate how to do without knowing the format of input.txt, mainly about the string and the bool – bruno Apr 25 '20 at 14:51
  • This is just an example, my struct have about 10 members – Phineas Apr 25 '20 at 14:52
  • @Phineas as you can see in my answer the format of the input file is very important – bruno Apr 25 '20 at 15:39
  • Search for serialization. That is what you want to do. Not so easy in C++. Because you want to have it generic. Maybe read about JSOn and JSON parsers. – A M Apr 25 '20 at 17:06

3 Answers3

2

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

bruno
  • 32,421
  • 7
  • 25
  • 37
0

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.

Kebberling
  • 72
  • 1
  • 7
  • I'm still learning C/C++, so want to discover ways to code, using `file>>` is simple, effective, but repetitive. I want to generalize my function to use many structs, and I know a bit about template, but I don't think it would help – Phineas Apr 25 '20 at 16:09
  • @Phineas trust me `file >>` is the right way to do, it is sure, short, easy to read, easy to modify if you add/remove/modify an attribute in your *struct*. Of course it is possible to do differently, with an array where for each pointer to attribute is associated the way to read, and looping on it, but the code will be longer, difficult to understand and not sure. To do complicated and unsure is dirty programming, this is what 'wrong' programmers do, to be like them is your goal ? – bruno Apr 25 '20 at 16:38
0

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);
}
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271