3

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?

1
  • 3
    There are many ways to do that. A trivial solution would be to just have your function take a bool argument that lets it know whether to do a or b. Commented May 15, 2020 at 19:17

4 Answers 4

5

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.

Sign up to request clarification or add additional context in comments.

Comments

3

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

Comments

0

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;
    }
}

Comments

0

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)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.