The below method will concatenate all warnings into one string. It works but obviously need to create almost the same method again for info and error.
struct MyItem
{
std::vector<std::string> errors;
std::vector<std::string> warnings;
std::vector<std::string> infos;
};
std::vector<MyItem> items;
std::string GetWarnings()
{
std::string str;
for (auto item : items)
{
for (auto warn : item.warnings)
{
str = warn;
str = " ";
}
}
return str;
}
What would be a good generic way to implement one "Concatenate" method? One solution would be to define an enum (error\warning\item), pass it as input argument and make a switch case on argument value. Any more elegant solutions?
CodePudding user response:
You can use a pointer to member to extract a specific field from an object:
auto concatenate(
const std::vector<MyItem>& items,
const std::vector<std::string> MyItem::* member
) {
std::string str;
for (const auto& item : items) {
for (const auto& element : item.*member) {
str = element;
str = " ";
}
}
return str;
}
Which can be used like so:
int main() {
std::vector<MyItem> items{
{{"err11", "err12"}, {"w11"}, {"i11", "i12", "i13"}},
{{"err21"}, {"w21", "w22", "w23", "w24"}, {"i21"}}
};
std::cout << "all errors: " << concatenate(items, &MyItem::errors) << '\n'
<< "all warnings: " << concatenate(items, &MyItem::warnings) << '\n'
<< "all infos: " << concatenate(items, &MyItem::infos) << '\n';
}
And, if you have members of different types in your struct (note that the above solution works only for vector
s of string
s), you can turn concatenate
into a function template
:
struct MyItem {
std::vector<std::string> errors;
std::vector<std::string> warnings;
std::vector<std::string> infos;
std::vector<char> stuff; // added
};
template <typename T> // not a function *template*
auto concatenate(
const std::vector<MyItem>& items,
const T MyItem::* member
) {
std::string str;
for (const auto& item : items) {
for (const auto& element : item.*member) {
str = element;
str = " ";
}
}
return str;
}
int main() {
std::vector<MyItem> items{
{{"err11", "err12"}, {"w11"}, {"i11", "i12", "i13"}, {'1' ,'2'}},
{{"err21"}, {"w21", "w22", "w23", "w24"}, {"i21"}, {'3'}}
};
std::cout << "all errors: " << concatenate(items, &MyItem::errors) << '\n'
<< "all warnings: " << concatenate(items, &MyItem::warnings) << '\n'
<< "all infos: " << concatenate(items, &MyItem::infos) << '\n'
<< "all stuffs: " << concatenate(items, &MyItem::stuff) << '\n';
}
Note that I also changed your auto item
occurrences in your for()
loops to const auto& item
s in order to avoid unncecessary copies.
Alternatively, you can use a projection to extract your elements. In this implementation, we use a template
to accept any type of a function that will take your MyItem
and return the desired element:
template <typename Proj>
auto concatenate(
const std::vector<MyItem>& items,
Proj projection
) {
std::string str;
for (const auto& item : items) {
for (const auto& element : projection(item)) {
str = element;
str = " ";
}
}
return str;
}
int main() {
std::vector<MyItem> items{
{{"err11", "err12"}, {"w11"}, {"i11", "i12", "i13"}, {'1' ,'2'}},
{{"err21"}, {"w21", "w22", "w23", "w24"}, {"i21"}, {'3'}}
};
auto errors_projection =
[](const MyItem& item) -> const std::vector<std::string>& {
return item.errors;
};
auto warnings_projection =
[](const MyItem& item) -> const std::vector<std::string>& {
return item.warnings;
};
auto infos_projection =
[](const MyItem& item) -> const std::vector<std::string>& {
return item.infos;
};
auto stuff_projection =
[](const MyItem& item) -> const std::vector<char>& {
return item.stuff;
};
std::cout << "all errors: " << concatenate(items, errors_projection) << '\n'
<< "all warnings: " << concatenate(items, warnings_projection) << '\n'
<< "all infos: " << concatenate(items, infos_projection) << '\n'
<< "all stuffs: " << concatenate(items, stuff_projection) << '\n';
}