I am currently working on a bigger project where I found a part of it which seems very ugly to me from the code.
I stripped out the algorithm and made a working code, so it can be reviewed.
I have a data structure which contains several containers with several different objects:
struct Data {
std::vector<TypeA> a;
std::vector<TypeB> b;
std::vector<TypeC> c;
}
TypeA
, TypeB
and TypeC
are all different objects with different sizes in the real programm. The comman part is they all have a no
:
struct TypeA {
std::size_t no;
std::string data;
std::string data2;
// more data member etc...
};
struct TypeB {
std::size_t no;
std::string data;
std::string data2;
// more data member etc...
};
struct TypeC {
std::size_t no;
std::string data;
std::string data2;
// more data member etc...
};
All Containers are ordered by ascending no
:
Data d;
d.a.push_back(TypeA{ 1, "1" , "11" });
d.c.push_back(TypeC{ 2, "2", "12" });
d.b.push_back(TypeB{ 3, "3", "13" });
d.a.push_back(TypeA{ 4, "4", "14" });
d.c.push_back(TypeC{ 5, "5", "15" });
d.b.push_back(TypeB{ 6, "6", "16" });
All Numbers are unique, e.g. there's no no==1
in b
or c
. The index numbers are consecutive and fill the data structures.
I want to iterate over the whole struct Data
by the no
of the different data members and do something with the additional shared things the containers have.
To emphasize the ugliness, I sometimes print data1
and the other time data2
but the iteration is exactly the same.
How can the algorithm be improved?
My idea would be writing an custom iterator for Data
which iterates by the no
. But I don't know what is the best approach.
I'm open to any suggestions.
The complete code:
example.cpp
#include <vector>
#include <limits>
#include <iostream>
#include <string>
struct TypeA {
std::size_t no;
std::string data;
std::string data2;
// more data member etc...
};
struct TypeB {
std::size_t no;
std::string data;
std::string data2;
// more data member etc...
};
struct TypeC {
std::size_t no;
std::string data;
std::string data2;
// more data member etc...
};
// In reality TypeA, TypeB and TypeC are not exactly the same
struct Data {
std::vector<TypeA> a;
std::vector<TypeB> b;
std::vector<TypeC> c;
// In reality there are not only 3 values
// but > 20 to print consecutively by no
void print_data_in_order_of_no();
void print_data2_in_order_of_no();
};
void Data::print_data_in_order_of_no()
{
enum class It_out {
none,
a,
b,
c,
};
auto it_a = a.begin();
auto it_b = b.begin();
auto it_c = c.begin();
std::size_t current_no = 0;
auto it_out = It_out::none;
do {
auto possible_new_no = std::numeric_limits<size_t>::max();
it_out = It_out::none;
if (it_a != a.end() &&
it_a->no > current_no &&
it_a->no < possible_new_no) {
possible_new_no = it_a->no;
it_out = It_out::a;
}
if (it_b != b.end() &&
it_b->no > current_no &&
it_b->no < possible_new_no) {
possible_new_no = it_b->no;
it_out = It_out::b;
}
if (it_c != c.end() &&
it_c->no > current_no &&
it_c->no < possible_new_no) {
possible_new_no = it_c->no;
it_out = It_out::c;
}
current_no = possible_new_no;
switch (it_out) {
case It_out::a:
std::cout << it_a->data << '\n';
++it_a;
break;
case It_out::b:
std::cout << it_b->data << '\n';
++it_b;
break;
case It_out::c:
std::cout << it_c->data << '\n';
++it_c;
break;
case It_out::none:
break;
default:
throw std::runtime_error(
"void print_data_in_order_of_no()"
"impossible enum type"
);
}
} while (it_out != It_out::none);
}
void Data::print_data2_in_order_of_no()
{
enum class It_out {
none,
a,
b,
c,
};
auto it_a = a.begin();
auto it_b = b.begin();
auto it_c = c.begin();
std::size_t current_no = 0;
auto it_out = It_out::none;
do {
auto possible_new_no = std::numeric_limits<size_t>::max();
it_out = It_out::none;
if (it_a != a.end() &&
it_a->no > current_no &&
it_a->no < possible_new_no) {
possible_new_no = it_a->no;
it_out = It_out::a;
}
if (it_b != b.end() &&
it_b->no > current_no &&
it_b->no < possible_new_no) {
possible_new_no = it_b->no;
it_out = It_out::b;
}
if (it_c != c.end() &&
it_c->no > current_no &&
it_c->no < possible_new_no) {
possible_new_no = it_c->no;
it_out = It_out::c;
}
current_no = possible_new_no;
switch (it_out) {
case It_out::a:
std::cout << it_a->data2 <<'\n';
++it_a;
break;
case It_out::b:
std::cout << it_b->data2 << '\n';
++it_b;
break;
case It_out::c:
std::cout << it_c->data2 << '\n';
++it_c;
break;
case It_out::none:
break;
default:
throw std::runtime_error(
"void print_data2_in_order_of_no()"
"impossible enum type"
);
}
} while (it_out != It_out::none);
}
int main()
{
Data d;
d.a.push_back(TypeA{1, "1" , "11"});
d.a.push_back(TypeA{ 4, "4", "14" });
d.b.push_back(TypeB{ 3, "3", "13" });
d.b.push_back(TypeB{ 6, "6", "16" });
d.c.push_back(TypeC{ 2, "2", "12" });
d.c.push_back(TypeC{ 5, "5", "15" });
d.print_data_in_order_of_no();
d.print_data2_in_order_of_no();
std::cin.get();
}
The code for review is the Data
struct and its members.
no
used for something else or just for indexing/identification? If the latter, moveno
out ofTypeX
and joinTypeA
,TypeB
,TypeC
into a separate structure and use this structure in the container. \$\endgroup\$TypeA
TypeB
TypeC
were only one member is occupied at the time? \$\endgroup\$std::vector<std::variant<TypeA, TypeB, TypeC>>
(where those three types have nono
member since they are all explicitly ordered inside the container). \$\endgroup\$std::variant
here, since he needs all of theTypeA, TypeB, TypeC
containers to be valid at the same time. \$\endgroup\$std::variant
was exactly what i was looking for the whole day. In combination with std::visit i can perform any operation necessary. \$\endgroup\$