Reuse code in C++ functions that access different attributes of struct

Viewed 104

I have a struct

struct doodle
{
  int x;
  int y;
};

and 2 methods that are identical, except they make use of different attributes of struct doodle:

void ProcessDoodlesHorizontally(std::vector<struct doodle>& v_doodles)
{
    for (unsigned int i=0; i<v_doodles.size(); i++)
    {
      int x = v_doodles.at(i).x;
      std::cout<<x<<std::endl;
    }
}

void ProcessDoodlesVertically(std::vector<struct doodle>& v_doodles)
{
    for (unsigned int i=0; i<v_doodles.size(); i++)
    {
      int y = v_doodles.at(i).y;
      std::cout<<y<<std::endl;
    }
}

I would like to make a function ProcessDoodlesGeneric which can take as argument info about whether I am interested in doodle.x or doodle.y. Is this possible? If not, what are alternative ways to reuse code in this example?

4 Answers

Since the only difference between the functions is which data member is used, you can pass in a pointer to the data member (this works here since both data members are of the same type):

void Process(std::vector<struct doodle>& v_doodles, int doodle::* field)
{
  for (auto &doodle : v_doodles)
      std::cout<< doodle.*field << std::endl;
}

void ProcessDoodlesHorizontally(std::vector<struct doodle>& v_doodles)
{
  Process(v_doodles, &doodle::x);
}

void ProcessDoodlesVertically(std::vector<struct doodle>& v_doodles)
{
  Process(v_doodles, &doodle::y);
}

Note that I cleaned up the for loop a little to make it easier to read.

Here's a demo.

A simple bool parameter works to decide which attribute to use in this case, since there are only two options.

If you have a more complicated situation though, you could use a pointer-to-member:

void ProcessDoodlesGeneric(std::vector<doodle>& v_doodles, int doodle::*member)
{
    for (unsigned int i=0; i<v_doodles.size(); i++)
    {
      int member_val = v_doodles.at(i).*member;
      std::cout << member_val << std::endl;
    }
}

int main()
{
    std::vector<doodle> doodles = { {1, 2}, {3, 4}, {5, 6} };
    ProcessDoodlesGeneric(doodles, &doodle::x);
    ProcessDoodlesGeneric(doodles, &doodle::y);
}

Live Demo

Seeing as the only difference between the two functions is which property you read from, you could add a boolean argument to your function that determines whether you're reading from the x or y property.

void ProcessDoodlesGeneric(std::vector<struct doodle>& v_doodles, bool horizontal)
{
    for (unsigned int i=0; i<v_doodles.size(); i++)
    {
      int output;
      if(horizontal) output = v_doodles.at(i).x;
      else output = v_doodles.at(i).y
      std::cout<<output<<std::endl;
    }
}

A simple way to do this is to merge the functions. There is one extra variable which is the direction:

void ProcessDoodles(std::vector<struct doodle>& v_doodles, direction_enum dir)
{
    for (unsigned int i=0; i<v_doodles.size(); i++)
    {
        int val;
        if (dir == direction_enum::vertical)
        {
           val = v_doodles.at(i).y;
        }
        else
        {
           val = v_doodles.at(i).x;
        }
        std::cout << val << std::endl;
    }
}

Where direction_enum could be an enum or a int or a bool or whatever.

Note: this is slower since you incur an extra "if" but I am guessing branch prediction can mitigate this. There are probably better ways - this is just a very simple way to merge a function - you take all the variables and they become the function parameters... In fact some IDEs will exactly do this for you (under refactor menus)

Related