C++ Serializing a std::map to a file - c++

I have a C++ STL map, which is a map of int and customType.
The customType is a struct, which has string and a list of string, How can i serialize this to a file.
sample struct:
struct customType{
string;
string;
int;
list<string>;
}

If you are not afraid of BOOST, try BOOST Serialize:
(template code, here can be some errors...)
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include <boost/serialization/map.hpp>
#include <boost/serialization/string.hpp>
#include <boost/serialization/list.hpp>
struct customType{
string string1;
string string2;
int i;
list<string> list;
// boost serialize
private:
friend class boost::serialization::access;
template <typename Archive> void serialize(Archive &ar, const unsigned int version) {
ar & string1;
ar & string2;
ar & i;
ar & list;
}
};
template <typename ClassTo>
int Save(const string fname, const ClassTo &c)
{
ofstream f(fname.c_str(), ios::binary);
if (f.fail()) return -1;
boost::archive::binary_oarchive oa(f);
oa << c;
return 0;
}
Usage:
Save< map<int, customType> >("test.map", yourMap);

A simple solution is to output each member on a line on its own, including all the strings in the list. Each record start with the key to the map, and ends with a special character or character sequence that can not be in the list. This way you can read one line at a time, and know the first line is the map key, the second line the first string in the structure and so on, and when you reach your special record-ending sequence you know the list is done and it's time for the next item in the map. This scheme makes the files generated readable, and editable if you need to edit them outside the program.

C++ doesn't have reflection capabilities like Java and others, so there's no 'automatic' way of doing that. You'll have to do all the work yourself: open the file, output each element in a loop, and close the file. Also there's no standard format for the file, you'd need to define one that meets your needs. Of course, there are libraries out there to help in this, but they aren't part of the language. Take a look at this question:
Is it possible to automatically serialize a C++ object?
Also take a look at:
http://s11n.net/

If you are asking this, then probably you already know that you cannot serialize this by means of:
file.write( (const char *) &mapOfCustom, sizeof( mapOfCustom ) );
The problem has to do with complex objects (and in C++, even a string variable is a complex object), i.e., those objects that are not self-contained. Actually, even simple serialization has problems, which range from platform compatibilty to even compiler compatibilty (different paddings, etc.).
One way to go is use a simple XML library such as tinyXML:
http://www.grinninglizard.com/tinyxml/
And write save to XML, and restore from XML procedures.

You can try this: cxx-prettyprint

Hi I wrote a standalone C11 header to achieve this. Your example
of a map of custom classes, I just added - to make sure it worked 8)
https://github.com/goblinhack/simple-c-plus-plus-serializer
#include "c_plus_plus_serializer.h"
class Custom {
public:
int a;
std::string b;
std::vector c;
friend std::ostream& operator<<(std::ostream &out,
Bits my)
{
out << bits(my.t.a) << bits(my.t.b) << bits(my.t.c);
return (out);
}
friend std::istream& operator>>(std::istream &in,
Bits my)
{
in >> bits(my.t.a) >> bits(my.t.b) >> bits(my.t.c);
return (in);
}
friend std::ostream& operator<<(std::ostream &out,
class Custom &my)
{
out << "a:" << my.a << " b:" << my.b;
out << " c:[" << my.c.size() << " elems]:";
for (auto v : my.c) {
out << v << " ";
}
out << std::endl;
return (out);
}
};
static void save_map_key_string_value_custom (const std::string filename)
{
std::cout << "save to " << filename << std::endl;
std::ofstream out(filename, std::ios::binary );
std::map< std::string, class Custom > m;
auto c1 = Custom();
c1.a = 1;
c1.b = "hello";
std::initializer_list L1 = {"vec-elem1", "vec-elem2"};
std::vector l1(L1);
c1.c = l1;
auto c2 = Custom();
c2.a = 2;
c2.b = "there";
std::initializer_list L2 = {"vec-elem3", "vec-elem4"};
std::vector l2(L2);
c2.c = l2;
m.insert(std::make_pair(std::string("key1"), c1));
m.insert(std::make_pair(std::string("key2"), c2));
out << bits(m);
}
static void load_map_key_string_value_custom (const std::string filename)
{
std::cout << "read from " << filename << std::endl;
std::ifstream in(filename);
std::map< std::string, class Custom > m;
in >> bits(m);
std::cout << std::endl;
std::cout << "m = " << m.size() << " list-elems { " << std::endl;
for (auto i : m) {
std::cout << " [" << i.first << "] = " << i.second;
}
std::cout << "}" << std::endl;
}
void map_custom_class_example (void)
{
std::cout << "map key string, value class" << std::endl;
std::cout << "============================" << std::endl;
save_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
load_map_key_string_value_custom(std::string("map_of_custom_class.bin"));
std::cout << std::endl;
}
Output:
map key string, value class
============================
save to map_of_custom_class.bin
read from map_of_custom_class.bin
m = 2 list-elems {
[key1] = a:1 b:hello c:[2 elems]:vec-elem1 vec-elem2
[key2] = a:2 b:there c:[2 elems]:vec-elem3 vec-elem4
}
Let me know if this helps - or you find bugs. It's quite a simple serializer and really just a learning tool for me. Heavier weight approaches like Cereal might work for you better.

Related

How to add a list to a string? [duplicate]

This question already has answers here:
Printing out contents of a list from the c++ list library [duplicate]
(5 answers)
Closed 7 months ago.
I am trying to add a list to a string.
int main() {
std::cout << "Hello, welcome to Jay's Coffee!!\n";
std::string name; std::cout <<"What is your name "; std::cin >> name;
std::cout <<"Hello " << name << ", thank you so much for coming in today";
std::list <std::string> menu = {"Black Coffee" "Espresso" "Latte" "Cappucino"};
std::cout << name <<",what would you like from our menu today? Here is what we are serving.\n" << menu;
}
Returns
invalid operands to binary expression ('basic_ostream<char>' and 'std::list<std::string>' (aka 'list<basic_string<char>>'))
There is no operator<< for lists. You have to write a loop. For example
for (auto& item : menu)
{
std::cout << item << '\n';
}
If you think about it it's obvious why you have to do this yourself. How are you doing to separate the list items? I've chosen to put each item on a new line. You might choose to separate them with commas or spaces or some fancy format. Because there is no obvious single way to print a list there is no predefined way in the C++ library.
You should write code this way. In c++, you can't print a list directly.
#include <string>
#include <list>
using namespace std;
int main() {
cout << "Hello, welcome to Jay's Coffee!!\n";
string name;
cout <<"What is your name ";
cin >> name;
cout <<"Hello " << name << ", thank you so much for coming in today";
list <string> menu = {"Black Coffee", "Espresso", "Latte", "Cappucino"};
cout << name <<",what would you like from our menu today? Here is what we are serving.\n" ;
for ( string& s : menu )
{
cout << s << '\n';
}
}
The error message means that the operator << that you are trying to use with your object menu of the type std::list<std::string> is not defined for the class std::list<std::string>.
Also you need to separate strings in the initializer list with commas.
std::list <std::string> menu = {"Black Coffee", "Espresso", "Latte", "Cappucino"};
Otherwise the list will contain only one string due to the concatenation of string literals.
You could define such an operator as shown in the demonstration program below.
#include <iostream>
#include <string>
#include <list>
std::ostream & operator <<( std::ostream &os, const std::list<std::string>& lst )
{
for ( const auto &s : lst )
{
os << s << '\n';
}
return os;
}
int main()
{
std::list <std::string> menu =
{
"Black Coffee", "Espresso", "Latte", "Cappucino"
};
std::cout << menu;
}
The program output is
Black Coffee
Espresso
Latte
Cappucino
Or just use the range-based for loop directly in main like
std::cout << name <<",what would you like from our menu today? Here is what we are serving.\n";
for ( const auto &s : menu )
{
std::cout << s << '\n';
}
Or place the range-based for loop in a separate function similar to the operator << shown above.
First thing, you didn't define a list: in the declaration of menu, initializer-list elems were not separated by comma (,).
To make it reusable, I'd do it something like this:
#include <iostream>
#include <list>
static const std::string list_sep = ", ";
template<typename C> struct FormattedContainer
{
FormattedContainer(const C& cont, const std::string& sep = list_sep)
: cont_(cont)
, sep_(sep) {}
friend std::ostream& operator<<(std::ostream& os, const FormattedContainer& fc)
{
bool first = true;
for (auto&& e : fc.cont_)
{
if (first)
{
os << e;
first = false;
}
else
{
os << fc.sep_ << e;
}
}
return os;
}
const C& cont_;
const std::string sep_;
};
template<typename C>
auto make_fc(const C& cont, const std::string& sep = list_sep)
-> FormattedContainer<C>
{
return FormattedContainer<C>(cont, sep);
}
int main() {
std::list <std::string> menu = {"Black Coffee", "Espresso", "Latte", "Cappucino"};
std::cout << "What would you like from our menu today? Here is what we are serving.\n" << make_fc(menu);
}
This way, you don't need to define operator<< for something in std namespace (which might result in ambiguous calls as others might also define it, don't need to import another namespace, just simply call a wrapper around the type. You can use it with basically any container or iterable type using this method.
All other answers are correct but here is the better way to do the same
#include <iostream>
#include <algorithm>
#include <list>
template <typename T>
std::ostream & operator << (std::ostream & os, const std::list<T> & vec){
std::for_each (vec.begin () , vec.end() , [&](const auto& val){
std::cout << val << " ";
});
return os;
}
int main () {
std::list <std::string> menu = {"Black Coffee", "Espresso", "Latte", "Cappucino"};
std::cout << menu << "\n";
return 0;
}

How to use pointer with struct to refer to the field of each struct

Below code is the normal way to get the input from a text and store it in an array in a structure.
Wanted to ask how can i use pointer to store all these data into the array of structure ? Like p1->Years (this is without array, but how can i apply this to way of writing in below code)
Any better suggestion to use pointer to take in the input?
int years = 4;
struct maju_company {
int Year;
float quarter1, quarter2, quarter3, quarter4, total_sales, average_sales;
};
int main() {
string line;
maju_company p1[years];
fstream yeecinnfile("MajuSales.txt");
if(yeecinnfile.is_open()) {
//ignoring the first four line of code and store the rest of the code
string line1,line2,line3,line4;
getline(yeecinnfile,line1);
getline(yeecinnfile,line2);
getline(yeecinnfile,line3);
getline(yeecinnfile,line4);
while(!yeecinnfile.eof()) {
for(int i =0; i<years; i++) {
yeecinnfile>>p1[i].Year>>p1[i].quarter1>>p1[i].quarter2>>p1[i].quarter3>>p1[i].quarter4;
}
}
for(int i =0; i<years; i++) {
cout<<p1[i].Year<<setw(10)<<p1[i].quarter1<<setw(10)<<p1[i].quarter2<<setw(10)<<p1[i].quarter3<<setw(10)<<p1[i].quarter4<<endl;
}
cout<<endl;
}
}
I see nothing wrong with the way you do this.
However, you could create a pointer to each record inside the loop
maju_company* p = &p1[i];
and then use p-> instead of p1[i]., but I really don't see this as an improvement.
If the reading loop looks too complicated, I would rather move the code to a separate function, perhaps
void read_record(maju_company& company);
or maybe
maju_company read_record();
and then only have to handle a single company inside the function (so no indexing and no ponters there).
I think you wouldn't need pointers at all for your example.
Use a std::vector to hold all your data and then there are other
things from C++ I think you should learn to use, example here :
(if you have questions let me know)
#include <iostream>
#include <string>
#include <vector>
#include <fstream>
// dont use : using namespace std;
struct maju_company_data
{
int year;
float quarter1, quarter2, quarter3, quarter4, total_sales, average_sales;
};
// describe how to stream data to an output stream (like std::cout)
std::ostream& operator<<(std::ostream& os, const maju_company_data& data)
{
os << "-----------------------------------------------------\n";
os << "Company data for year : " << data.year << "\n";
os << "Quarter 1 : " << data.quarter1 << "\n";
os << "Quarter 2 : " << data.quarter1 << "\n";
os << "Quarter 3 : " << data.quarter1 << "\n";
os << "Quarter 4 : " << data.quarter1 << "\n";
os << "\n";
return os;
}
int main()
{
// no need to manage pointers yourself use a vector
std::vector<maju_company_data> company_yearly_data; // give variables a meaningful name
std::ifstream ifile("MajuSales.txt"); // ifstream your using file as input
std::string line1, line2, line3, line4;
// ignore first line
ifile >> line1;
while (ifile >> line1 >> line2 >> line3 >> line4) // probably you need to read a few more lines here
{
maju_company_data data;
// convert read strings to numbers
data.year = std::stoi(line1);
data.quarter1 = std::stof(line2);
data.quarter2 = std::stof(line3);
data.quarter3 = std::stof(line4);
//..
//data.quarter4 = std::stof(line5);
//data.total_sales = std::stof(line6);
company_yearly_data.push_back(data);
};
// this is a range based for loop
// it is prefered since you cant go out of bounds
// const auto& means that data will be an unmodifiable
// reference to each of the structs stored in the vector
for (const auto& data : company_yearly_data)
{
std::cout << data; // since we overloaded << this loop will be nice and clean
}
return 0;
}
A C++ approach to this to overload the istream operator>> and ostream operator<< for your specific type. E.g.
#include <algorithm>
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <string>
static constexpr auto years{4};
struct maju_company {
int Year{};
float quarter1{}, quarter2{}, quarter3{}, quarter4{};
float total_sales{}, average_sales{}; // ALWAYS init your floats.
};
auto& operator>>(std::istream& is, maju_company& mc) {
is >> mc.Year
>> mc.quarter1 >> mc.quarter2 >> mc.quarter3 >> mc.quarter4
>> mc.total_sales >> mc.average_sales;
return is;
}
auto& operator<<(std::ostream& os, maju_company const& mc) {
os << mc.Year
<< std::setw(10) << mc.quarter1
<< std::setw(10) << mc.quarter2
<< std::setw(10) << mc.quarter3
<< std::setw(10) << mc.quarter4;
return os;
}
You can then go on to use the type using the std library, e.g.
int main() {
auto p1{std::array<maju_company, years>{}};
{
auto fs{std::fstream("MajuSales.txt")};
if (!fs.is_open()) return -1;
{
// throw away 4 lines
auto dummy{std::string{}};
for (auto i{0}; i < 4; ++i) getline(fs, dummy);
}
std::copy_n(std::istream_iterator<maju_company>{fs},
years,
begin(p1));
}
std::copy(cbegin(p1), cend(p1),
std::ostream_iterator<maju_company>{std::cout, "\n"});
}

Capture a functions standard output and write it to a file

What I try to do is to write all output inside a function into a file. Maybe I need a way to assign all output (not only arrays) in test_func to some kind of variable so that I can return it, but I can't figure out.
#include <iostream>
#include <fstream>
#include <functional>
using namespace std;
void test_func()
{
int a[] = {20,42,41,40};
int b[] = {2,4,2,1};
cout << "Below is the result: "<< endl;
for (int i=0; i<4; i++){
cout << "***********************" << endl;
cout << a[i] << " : " << b[i] <<endl;
cout << "-----------------------" << endl;
}
}
void write_to_file(function<void()>test_func)
{
ofstream ofile;
ofile.open("abc.txt");
ofile << test_func(); // This is not allowed
ofile.close();
}
int main()
{
write_to_file(test_func);
return 0;
}
I need to get all output from test_func instead of only the array a and b, because I have multiple functions in different formats, which are all needed to write into the file using same function write_to_file.
Is there any logical way to do this? (or alternative to function?)
Here is some code that will work the way you want. You have to replace std::couts current rdbuf() with the one of the file streams, and reset it afterwards:
void write_to_file(function<void()>test_func) {
ofstream ofile;
ofile.open("abc.txt");
std::streambuf* org = cout.rdbuf(); // Remember std::cout's old state
cout.rdbuf(ofile.rdbuf()); // Bind it to the output file stream
test_func(); // Simply call the anonymous function
cout.rdbuf(org); // Reset std::cout's old state
ofile.close();
}
Here you can see it running as you intended: Demo
To overcome the problem with the varying function signatures, you can use a delegating lambda function:
void test_func2(double a, int b) {
cout << a << " * " << b << " = " << (a * b) << endl;
}
int main() {
// Create a lambda function that calls test_func2 with the appropriate parameters
auto test_func_wrapper = []() {
test_func2(0.356,6);
};
write_to_file(test_func_wrapper); // <<<<< Pass the lambda here
// You can also forward the parameters by capturing them in the lambda definition
double a = 0.564;
int b = 4;
auto test_func_wrapper2 = [a,b]() {
test_func2(a,b);
};
write_to_file(test_func_wrapper2);
return 0;
}
Demo
You can even do this with a little helper class, which generalizes the case for any std::ostream types:
class capture {
public:
capture(std::ostream& out_, std::ostream& captured_) : out(out_), captured(captured_), org_outbuf(captured_.rdbuf()) {
captured.rdbuf(out.rdbuf());
}
~capture() {
captured.rdbuf(org_outbuf);
}
private:
std::ostream& out;
std::ostream& captured;
std::streambuf* org_outbuf;
};
void write_to_file(function<void()>test_func)
{
ofstream ofile;
ofile.open("abc.txt");
{
capture c(ofile,cout); // Will cover the current scope block
test_func();
}
ofile.close();
}
Demo
So regarding your comment:
Sure, but I will require something to store those cout, or maybe there's another completely different way instead of using test_func() for the process?
We have everything at hand now to do this
#include <iostream>
#include <fstream>
#include <functional>
#include <string>
#include <sstream>
using namespace std;
void test_func1(const std::string& saySomething) {
cout << saySomething << endl;
}
void test_func2(double a, int b) {
cout << "a * b = " << (a * b) << endl;
}
class capture {
public:
capture(std::ostream& out_, std::ostream& captured_) : out(out_), captured(captured_), org_outbuf(captured_.rdbuf()) {
captured.rdbuf(out.rdbuf());
}
~capture() {
captured.rdbuf(org_outbuf);
}
private:
std::ostream& out;
std::ostream& captured;
std::streambuf* org_outbuf;
};
int main() {
std::string hello = "Hello World";
auto test_func1_wrapper = [hello]() {
test_func1(hello);
};
double a = 0.356;
int b = 6;
auto test_func2_wrapper = [a,b]() {
test_func2(a,6);
};
std::stringstream test_func1_out;
std::stringstream test_func2_out;
std::string captured_func_out;
{ capture c(test_func1_out,cout);
test_func1_wrapper();
}
{ capture c(test_func2_out,cout);
test_func2_wrapper();
}
captured_func_out = test_func1_out.str();
cout << "test_func1 wrote to cout:" << endl;
cout << captured_func_out << endl;
captured_func_out = test_func2_out.str();
cout << "test_func2 wrote to cout:" << endl;
cout << captured_func_out << endl;
}
And the Demo of course.
The line ofile << test_func(); means that returned value of called test_func(); is directed to that stream. It doesn't do anything to actions done within function called. You may pass stream to the function though.
void test_func(ostream& outs)
{
outs << "Below is the result: "<< endl;
}
and call it with cout or ofile - any ostream as argument.
void write_to_file(function<void(ostream&)>test_func)
{
ofstream ofile;
ofile.open("abc.txt");
test_func(ofile); // This is not allowed
ofile.close();
}
But if the behaviour of function as stream manipulator is something what you want, you have to design a proper operator.
ostream& operator<< (ostream& o, void(*func)(ostream&) )
{
func(o);
return o;
}
Then you can write something like
cout << test_func << " That's all, folks\n";
Note, that test_func isn't called here, its id used as expression results in function's address being passed to operator<<.
Real stream manipulators (e.g. https://en.cppreference.com/w/cpp/io/manip/setw ) implemented not as functions , but as templates of functional objects, the argument of setw in line:
is >> std::setw(6) >> arr;
is actually argument of a constructor
What I try to do is to write all output inside a function into a file.
I often use a std::stringstream to act as a temporary repository for text, i.e. the ss holds and bundles all output into a 'buffer' (a text string) for delay'd output to the file.
For your test_func, you might add a ss reference parameter :
void test_func(std::stringsteam& ss)
{
int a[] = {20,42,41,40};
int b[] = {2,4,2,1};
cout << "Below is the result: "<< endl;
for (int i=0; i<4; i++){
ss << "***********************" << endl;
ss << a[i] << " : " << b[i] <<endl;
ss << "-----------------------" << endl;
}
}
A std::stringstream is essentially a ram-based ofile (with none of the hard disk overhead).
So you can run many test_func's, lump all the output together into one ss, and empty the ss content to the one file.
Or, you might invoke 1 test_func, output / append that ss contents to your ofile, then clear the ss for re-use.
You also might invoke 1 test func, output that ss contents to a unique ofile, then clear the ss and do the next test func, etc.
Note: a) std::stringstream uses one std::string as a working buffer, and b) std::string keeps its data in dynamic memory. I seldom worry about how big the ss gets. But, if you are worried, and have an estimate, you can easily use reserve to set the string size. Knowing this size will allow you to plan to control very big output files.
Next, consider keeping stringstream out of the test_func's, and instead keep it in the outer data gathering function:
void write_to_file(function<void()>test_func)
{
std::stringstream ss; // temporary container
test_func(ss); // add contributions
test_func2(ss); // add contributions
test_func3(ss); // add contributions
// ...
test_funcN(ss); // add contributions
// when all testing is complete, output concatenated result to single file
ofstream ofile;
ofile.open("abc.txt");
ofile << ss.str();
ofile.close();
}
int main()
{
write_to_file(test_func);
return 0;
}
Note: to empty a ss, I use 2 steps:
void ssClr(stringstream& ss) { ss.str(string()); ss.clear(); }
// clear data clear flags
Note: I encapsulate my coding efforts into one or more c++ classes. In my code, the ss objects are declared as a data attribute of my class, and thus accessible to all function attributes of that class, including each test_funci (i.e. no need to pass the ss)

writing the vector map to a file in Omnetpp

I am having problem in writing the vector map to a file. I would like to know the detail value inside the wsmdata. I know that inorder to access the detail information I need to use operator overloading like “std::ostream& operator<<(std::ostream& os, map& );” in header file as well as in .cc file. But I don’t know how to use it in detail to access the vector data or output the vector data in the file. I have bee stuck in this problem for a long time. Can anybody help ?
Here is the portion of codes:
.h file:
using std::map;
typedef std::vector<WaveShortMessage*> WaveShortMessages;
std::map<long,WaveShortMessages> receivedWarningMap;
.cc file:
// add warning message to received messages storage
receivedWarningMap[wsm->getTreeId()].push_back(wsm->dup());
std::cout<<"Wsm dup() values/ receivedWarningMap="<<wsm->dup()<<endl;
std::ofstream tracefile;
tracefile.clear();
tracefile.open("traceFile1.txt", std::ios_base::app);
for (UINT i = 0; i < receivedWarningMap[wsm->getTreeId()].size(); i++)
{
std::cout << receivedWarningMap[wsm->getTreeId()][i] << std::endl;
EV<< "MyID="<<getMyID()<< "Recepient ID"<<wsm->getRecipientAddress()<<"Neighbor ID="<< wsm->getSenderAddress()<< std::endl;
}
tracefile.close();
First of all define operator << for your class WaveShortMessage, for example this way:
std::ostream & operator<<(std::ostream &os, WaveShortMessage * wsm) {
os << "Recepient ID=" << wsm->getRecipientAddress() << "; ";
os << "Neighbor ID=" << wsm->getSenderAddress() << "; ";
// and any other fields of this class
//...
return os;
}
Then use the following code to write map to text file:
// remember to add this two includes at the beginning:
// #include <fstream>
// #include <sstream>
std::ofstream logFile;
logFile.open("log.txt"); // if exists it will be overwritten
std::stringstream ss;
for (auto it = receivedWarningMap.begin(); it != receivedWarningMap.end(); ++it) {
ss << "id=" << static_cast<int>(it->first) << "; wsms=";
for (auto it2 : it->second) {
ss << it2 << "; ";
}
ss << endl;
}
logFile << ss.str();
logFile.close();

Parsing a boost::variant with User Defined Datatype containing std::string using Spirit Qi

I'm trying to create a GPIB parser using Spirit.Qi. Sometimes the response can
be either an error or a normal response. This seemed like a good use case for
the alternative parser which yields a boost::variant; however, if one of the
variant types contains a string the code fails to compile. Here is a simplified
version that reproduces the error.
struct C1 {
std::string h{""};
int i{0};
};
BOOST_FUSION_ADAPT_STRUCT(C1, (std::string, h)(int, i))
struct C2 {
std::string h{""};
std::string c{};
};
BOOST_FUSION_ADAPT_STRUCT(C2, (std::string, h)(std::string, c))
using VariantType = boost::variant<C1, C2>;
int main() {
std::string s2{"C2:Zoo3"};
VariantType v1;
if(qi::parse(s1.begin(), s1.end(),
(qi::string("C1") >> ":" >> qi::int_) |
(qi::string("C2") >> ":" >> *qi::char_),
v1)) {
if(boost::get<C1>(&v1)) {
auto a1 = boost::get<C1>(v1);
std::cout << "Parsing Succeeded, a1 = " << a1.h << ":"
<< a1.i << std::endl;
}
else {
auto a2 = boost::get<C2>(v1);
std::cout << "Parsing Succeeded, a2 = " << a2.h << ":"
<< a2.c << std::endl;
}
}
else {
std::cout << "Parsing Failed" << std::endl;
}
return 0;
}
I've experimented with various parsers other than *qi::char_ (eg. qi::string) to no avail. If I change C2::c to a char it works ok for 1 char. The best workaround I have found so far is to change C2::c to a std::vector<char>, this works fine but isn't ideal. I also tried telling Qi that std::string is a container like here. But qi knows what a std::string is so I'm pretty sure it ignores my customization. I think this is all because std::string isn't a POD and wasn't supported in unions until the rules were relaxed, but it works with boost::variant when it isn't in a struct, and std::vector works. Any ideas/workarounds would be appreciated.
*Note: I didn't post the compiler errors as they are long and mangled and I figured this was a known issue of std::string and variants. If they would be helpful let me know and I'll post them.
There's nothing wrong with the code, as far as I can tell.
I've tested on c++1{1,4,y} and boost 1.{57,58}.0, using gcc {4.9,5.x} and clang++ 3.5.
I suspect you may have an awkward Boost verion. Try using qi::as_string[*qi::char_] there. ¹
Live On Coliru
#include <iostream>
#include <boost/fusion/adapted/struct.hpp>
#include <boost/spirit/include/qi.hpp>
struct C1 {
std::string h{""};
int i{0};
friend std::ostream& operator<<(std::ostream& os, C1 const& c1) {
return os << "C1 {h:'" << c1.h << "', i:'" << c1.i << "'}";
}
};
struct C2 {
std::string h{""};
std::string c{};
friend std::ostream& operator<<(std::ostream& os, C2 const& c2) {
return os << "C2 {h:'" << c2.h << "', c:'" << c2.c << "'}";
}
};
BOOST_FUSION_ADAPT_STRUCT(C1, (std::string, h)(int, i))
BOOST_FUSION_ADAPT_STRUCT(C2, (std::string, h)(std::string, c))
using VariantType = boost::variant<C1, C2>;
namespace qi = boost::spirit::qi;
int main() {
VariantType value;
for(std::string s1 : {
"C2:Zoo3",
"C1:1234"
})
{
if(qi::parse(s1.begin(), s1.end(),
(qi::string("C1") >> ":" >> qi::int_) |
(qi::string("C2") >> ":" >> *qi::char_),
value))
std::cout << "Parsing Succeeded: " << value << "\n";
else
std::cout << "Parsing Failed" << std::endl;
}
}
Prints
Parsing Succeeded: C2 {h:'C2', c:'Zoo3'}
Parsing Succeeded: C1 {h:'C1', i:'1234'}
¹ I don't recommend qi::attr_cast<> as I remember this having had an awkward bug in awkwardly old boost versions.