LarsA
LarsA

Reputation: 617

How to make a simple loop more generic

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?

Upvotes: 2

Views: 97

Answers (1)

Fureeish
Fureeish

Reputation: 13424

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>                   // now 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';
}

Upvotes: 3

Related Questions