Function pointer syntax.

Hello,

I have a piece of code that sorts data based on some metric. The some metric is something I now want to make flexible so I can easily switch between and compare metrics. To do this, I want to pass the function to use as a parameter to the method that does the sorting (and other stuff). However, I'm having problems figuring out the syntax. Also, I think my [temporary] organization of code is violating a lot of basic code design principles; I'll fix this later, but feel free to point it out if it could interfere with what I'm trying to do.


To make the function pointer passable, I defined the "typename" in the header where the function is located (it is part of a struct, "Data"):
1
2
// Below the struct definition of Data
typedef double (Data::*CostF)(unsigned l, double W) const;


The two example functions I want to use are defined in that struct ("Data"):
1
2
3
4
 // Inside the struct definition
inline double someExampleCost(unsigned l, double W) const {
    // Returns some basic calculation
}


The function that uses it is part of a different class (that holds a reference to the first class, in case that matters; I feel like I'm doing something odd here, because I'm binding a member function in the definition/passing, but never referencing the object). It looks like this:

1
2
3
4
5
6
7
8
9
10
11
 // Inside another class ("Foo")
inline void DoSomeStuff(double& ECost, double& TCost, CostF cost) {
// Irrelevant stuff here
    std::sort(vector.begin(), vector.end(), [&](unsigned a, unsigned b){
	return (*cost)(a, W) < (*cost)(b, W);
    });
// More irrelevant stuff here
}

The error shown is "operand of "*" must be a pointer". If I remove the '*':
[code]return cost(A, W) < cost(b, W);

the error becomes: "expression must have a (pointer-to-)function type."

The call to this function is, currently, just in the main function, as I'm just testing before I wrap it into real code. It looks like this:

1
2
3
4
// In main
Foo bar; // Make an object of the struct that has the "sorting" function
CostF costFunction = &Data::someExampleCost; // Bind to the Cost function
bar.DoSomeStuff(varA, varB, costFunction);


This bit shows no errors by itself.

So, my questions:
a) Clearly I'm missing the insight into Function Pointers. I'm comfortable with regular pointer stuff, but I can't wrap my head around FPs, partly due to the awkward syntax. Can someone help me "look" at FPs correctly?

b) I'm very uncomfortable with the fact that I'm binding a member function of a class, but never try to reference an actual object of that class. This is probably a big part of why it's not working, but I can't seem to bind a function belonging to a specific object. I thought of doing

1
2
3
4
// In the main again
Data d; // Construct the object, which contains big lookup tables 
Foo F(d); // Construct the object, which only holds a reference to a Data object
CostF costFunction = &d.someExampleCost; // Bind to the Cost function of that object 

but that doesn't work ("a pointer to a bound function may only be used to call the function").

Any and all help is appreciated, as always.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

struct data
{
    inline double someExampleCost( unsigned int, double ) const { return 0 ; }
};

using cost_fn_type = std::function< double( unsigned int, double ) > ;

void foo( double w, std::vector< unsigned int >& seq, const cost_fn_type& cost_fn )
{
    std::sort( std::begin(seq), std::end(seq),
               [&cost_fn,w]( unsigned int a, unsigned int b ) { return cost_fn( a, w ) < cost_fn( b, w ) ; } ) ;
}

int main()
{
   std::vector< unsigned int > seq { 0, 1, 2, 3 } ;
   data object ;
   using namespace std::placeholders ;
   foo( 100, seq, std::bind( &data::someExampleCost, &object, _1, _2 ) ) ;
}

To use raw pointer-to-member-function, see:
http://www.cplusplus.com/forum/general/148204/#msg777062
> I'm binding a member function of a class, but never try to reference an actual object of that class
good that you realize that.
1
2
3
CostF costFunction = &Data::someExampleCost;
Data d;
(d.*costFunction) (42, 13);


But the thing is, ¿do you really need the object? ¿wouldn't any instance work just as well?
¿then why is your function a member function?

So you may simply make it a global function, and then use it as cost(A, W)
@ne555:
No; the calculation itself is standard, but the data used is saved in an object (and multiple tables may exist).

Let's say the Data object looks like this:
1
2
3
4
5
6
7
struct Data {
    vector<double> myDataA;
    vector<double> myDataW;
    // myData is initialized with a file and values are read in; different instances of Data could technically exist, all based on a different file.
    
    double myCostFunction(unsigned A, double W) { return myDataA[A] + myDataW[A] * W; }
};


The goal of this code is to easily perform tests with multiple calculations (so different versions of myCostFunction; it represents an objective function for an optimization, which still has to be calibrated) as well as different data (the data is read in from a file and represents different technologies, each for which the data shows different behavior).

So, I'm not sure the CostFunctions should be a member function, but they should be somehow bound to an object, because the data is instance-specific. (I suppose a Data object [reference] could be a parameter of the calling function, but I feel like that would make the syntax even more confusing.)


@JLBorges: Thank you for the elaborate examples. It's going to take me some time to understand the syntax of placeholders, but it looks like a very elegant solution.

For the raw pointers, what is the difference between the "typedef" syntax and the "using" syntax? If I understand it, it does the exact same thing (define a "pointer-to-member-function" type with a given name), but my IDE won't accept it.

I tried to test it by replacing my typedef as such:
1
2
typedef double (Data::*CostF)(unsigned l, double W) const; // Old
using CostFu = double (Data::*)(unsigned, double) const; // New 


But it just underlines the "CostFu", saying "a class-qualified name is required".
Last edited on
> It's going to take me some time to understand the syntax of placeholders, but it looks like a very elegant solution.

We can also use a lambda expression:
1
2
// foo( 100, seq, std::bind( &data::someExampleCost, &object, _1, _2 ) ) ;
foo( 100, seq, [&object]( unsigned int a, double b){ return object.someExampleCost(a,b) ; } ) ;



> For the raw pointers, what is the difference between the "typedef" syntax and the "using" syntax?
> If I understand it, it does the exact same thing

Yes.

We could do away with the type alias altogether if we make the function a template:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <functional>
#include <vector>
#include <algorithm>

struct data
{
    inline double someExampleCost( unsigned int, double ) const { return 0 ; }
};

//using cost_fn_type = std::function< double( unsigned int, double ) > ;
typedef std::function< double( unsigned int, double ) > cost_fn_type ;

void foo( double w, std::vector< unsigned int >& seq, const cost_fn_type& cost_fn )
{
    std::sort( std::begin(seq), std::end(seq),
               [&cost_fn,w]( unsigned int a, unsigned int b ) { return cost_fn( a, w ) < cost_fn( b, w ) ; } ) ;
}

template < typename FN > void bar( double w, std::vector< unsigned int >& seq, FN&& fn )
{
    const auto cost_fn = std::forward<FN>(fn) ; // perfect forwarding, required only if fn is not passed by value
    std::sort( std::begin(seq), std::end(seq),
               [&cost_fn,w]( unsigned int a, unsigned int b ) { return cost_fn( a, w ) < cost_fn( b, w ) ; } ) ;
}

int main()
{
   std::vector< unsigned int > seq { 0, 1, 2, 3 } ;
   data object ;
   // using namespace std::placeholders ;
   // foo( 100, seq, std::bind( &data::someExampleCost, &object, _1, _2 ) ) ;
   foo( 100, seq, [&object]( unsigned int a, double b){ return object.someExampleCost(a,b) ; } ) ;
   bar( 100, seq, [&object]( unsigned int a, double b){ return object.someExampleCost(a,b) ; } ) ;
}
Topic archived. No new replies allowed.