Home > Software engineering >  How to make a simple loop more generic
How to make a simple loop more generic

Time:09-30

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 vectors of strings), 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& items 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';
}
  • Related