Reputation: 319
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
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
Reputation: 32596
There are several problems in your solution
std::string
when you want int
and bool
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 indexWhen 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
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