1

Fairly simple question:

I have a class that uses a (variable) heuristic function to perform a certain algorithm. This heuristic function should ideally be fed to the class constructor as some sort of pointer and implement the following declaration:

int heuristic_Function(GridLocation a, GridLocation b);

What is the best way to accomplish this? Ideally I would like to avoid additional classes and keep the code fairly self-contained (and yes, I am aware of things like delegates and the strategy pattern).

(This has probably been asked hundreds of times already but in different terms)

PeeC
  • 149
  • 3

3 Answers3

2

Well, as you said, you could store a function pointer:

struct Algo
{
    using HeurFn = int(GridLocation, GridLocation);

    Algo(HeurFn * heuristic) : heuristic_(heuristic) {}

    void Run()
    {
        // use "heuristic_(a, b)"
    }

    HeurFn * heuristic_;
};

Then instantiate it:

extern int my_fn(GridLocation, GridLocation);
Algo algo(my_fn);
algo.Run();

An alternative would be to pass the function directly to Run, in which case you could make Run a template and perhaps allow for inlining of the actual heuristic code, but you explicitly asked for the heuristic to be configured via the constructor.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • "An alternative would be to pass the function directly to `Run`" That does indeed sound like a more logical idea! I think I will be doing that. I can't believe it didn't occur to me to actually declare the "type" of a function pointer (I'm assuming that's what `int(GridLocation, GridLocation)` actually is. Coming from C# I've looked at C++ discussions about implementing delegates and this looks a lot simpler for most usages. Thanks! – PeeC Dec 21 '15 at 01:48
  • @PedroCaetano: It's the type of a *function*. A pointer is formed by appending a `*` to that name, `HeurFn` => `HeurFn *`. – Kerrek SB Dec 21 '15 at 01:54
2

Instead of old C function pointer, I would recommend std::function.

So you could write it like this

#include <functional>

struct algorithm{
    algorithm (std::function<int(GridLocation, GridLocation)> heuristic_function) :
    heuristic(heuristic_function) {}

    int do_something (GridLocation a, GridLocation b){
        return heuristic(a,b);
    }

private:
    std::function<int(GridLocation, GridLocation)> heuristic;
}

Advantages are the better readable syntax, and that the caller can use std::bind expressions.

Or you could just take the heuristic as a template, but then you would to either make your algorithm to just a function or write the type to every new instance. See https://stackoverflow.com/a/2156899/3537677

Community
  • 1
  • 1
Superlokkus
  • 4,731
  • 1
  • 25
  • 57
1

Things get really simple if only the method that does the computations needs the function, and you can forgo storing the function in the class itself. You can then parametrize the method on the type of the passed function, and you get full flexibility:

struct Calculate {
  template <typename F> int run(F && f) {
    return f(1, 2);
  }
};

int f1(int, int) { return 0; }

struct F2 {
  int operator()(int, int) { return 0; }
};

int main() {
  Calculate calc;
  // pass a C function pointer
  calc.run(f1);
  // pass a C++98 functor
  calc.run(F2());
  // pass a C++11 stateless lambda
  calc.run(+[](int a, int b) -> int { return a-b; });
  // pass a C++11 stateful lambda
  int k = 8;
  calc.run([k](int a, int b) -> int { return a*b+k; });
}

You don't need to manually spell out any types, and you can pass function-like objects that can be stateful.

The power of C++11 comes from the && syntax. There's more to it than meets the eye. In run's parameter, F is a deduced type, and && is a universal reference. That means that, depending on the context, it acts either as an lvalue-reference we know from C++98, or as an rvalue-reference.

The + operator applied to the lambda stresses that it is in fact stateless. Its uses forces a conversion from the abstract lambda type to a C function pointer. The type of the +[](int,int)->int {...} expression is int(*)(int,int). The use of the + operator is not necessary, I've only used it to underline the statelessness.

Kuba hasn't forgotten Monica
  • 95,931
  • 16
  • 151
  • 313