zenna
zenna

Reputation: 9176

Better design for using function pointers with varying arguments

I have an optimisation algorithm which finds the best partition of a graph.

There are many measures for the quality of a partition (the variable being optimised), so I thought it would be a good idea to use function pointers to these quality functions, and pass that into my optimisation algorithm function.

This works fine, but the problem is different quality functions take some different arguments.

For example one quality function is find_linearised_stability and it requires a markov_time parameter:

float find_linearised_stability(cliques::Graph<T> &my_graph, cliques::Partition &my_partition,
                               std::vector<float> &markov_times, std::vector<float> &stabilities)

and is used in the optimisation function :

cliques::find_optimal_partition_louvain(my_new_graph, markov_times, &cliques::find_linearised_stability);

however another quality function find_modularityrequires no markov_time parameter. Of course I could just include it as an argument and not use it in the function but that seems like bad practice, and would get unwieldy once I start adding a lot of different quality functions.

What is a better design for this kind of situation?

Upvotes: 2

Views: 444

Answers (3)

Benjamin Lindley
Benjamin Lindley

Reputation: 103713

Use function objects. One of those function objects can have a markov_time member that is passed in to the constructor:

struct find_linearised_stability {
    std::vector<float> & markov_times_;

    find_linearised_stability(std::vector<float> & markov_times)
        :markov_times_(markov_times)
    {}

    float operator () (cliques::Graph<T> &my_graph, cliques::Partition &my_partition,
                 std::vector<float> &stabilities)
    {
        // use markov_times_ in here, we didn't need to pass it since it's a member
    }
};

(you may need to make adjustments to constness/referenceness to suit your needs)

Then you can call your function like this:

cliques::find_optimal_partition_louvain(my_new_graph, cliques::find_linearised_stability(markov_times));

"what type for the function object do I use when declaring the ... function?"

Make it a function template that takes the function object type as a template parameter, thusly:

template<typename PR>
whatever find_optimal_partition_louvain(my_new_graph, PR & pr)
{
    ...
    pr(my_new_graph, partition, stabilities);
    ...
}

Upvotes: 6

peenut
peenut

Reputation: 3426

  1. parameter is not known before: add argument to every function (reference/pointer) that contains all info, every function uses whatever it needs
  2. parameter is known before: use boost::bind, e.g.:

sample source code:

#include <iostream>
#include <cstddef>
#include <algorithm>
#include <boost/bind.hpp>
using namespace std;

void output(int a, int b)
{
    cout << a << ", " << b << '\n';
}

int main()
{
    int arr[] = { 1, 2, 3, 4, 5 };
    for_each(arr, arr + 5, bind(output, 5, _1));
    return 0;
}

Outputs:

5, 1
5, 2
5, 3
5, 4
5, 5

Upvotes: 0

Edward Strange
Edward Strange

Reputation: 40867

Your only option is boost::bind or something like it stored in a boost::function or something like it.

If profiling shows that to be too slow then you'll be stuck with the "poor practice" version because any alternative is going to run afoul of UB and/or end up being just as 'slow' as the more reasonable alternative.

Upvotes: 2

Related Questions