Among other things, I teach programming; especially C++. Not in Academia (unfortunately), but Industry: mostly to experienced C programmers who need to upgrade.

 

In my 30 years of the subject, I don’t remember when I personally learned about function pointers. But in C (and C++), I found that powerful feature’s syntax messy, to either code or explain, so I looked for a better way – and found it. It works in literally every compiler I’ve tried it on, both in C and C++. Moreover, when I extrapolated it to C++ for pointers to members and pointers to member functions, the syntax held.

 

Yet I can honestly say that in all those years, no-one else I’ve talked to, let alone taught, has seen the syntax that I present below. Maybe I’m sheltered, or I’ve not met the right people – yet all the definitive works still show the more convoluted, albeit canonical syntax. So I now feel compelled to write: and my first foray is in the Wikipedia article dedicated to the subject. I reproduce it below, in full Wiki syntax, so that anyone can critique it; or even directly edit the article.

 

My question is simple: why doesn’t everyone declare pointers to functions, members, or member functions in the way I describe below? Is this breaking some rule? Is the two-step process somehow inefficient? I’ve even found style guides, for significant companies, that said (paraphrased): “Do not put pointers (‘*’) inside typedefs, to more clearly show what the type is for. Note that typedefs for pointers to functions can ignore this rule.” No! Keeping this rule, especially for pointers to functions (and members, and pointers to members), makes them even easier to understand!

 

To me, my most telling example is this two-step example, extracted from below:

 

// This defines 'Fn', a type of function that accepts a 'char' and returns an 'int'.

typedef int Fn(char c);

// This defines 'm', a pointer-to-member-of-'C' with type 'Fn',

// and assigns the address of 'C::Member' to it.

// You can read it right-to-left like all pointers:

// "'m' is a pointer to member of class 'C' of type 'Fn'"

Fn C::*m = &C::Member;

 

Finally, I reiterate an earlier question posed on this forum by Shachar Shemesh (https://lists.isocpp.org/std-discussion/2019/06/0059.php):

(Paraphrased) “Why is the precedence of ‘.*’ and ‘->*’ less than that of ‘.’ and ‘->’?”

 

https://en.wikipedia.org/wiki/Function_pointer#Alternate_C_and_C++_Syntax (Note this is a sub-heading: #6 at the moment)

 

== Alternate C and C++ Syntax ==

The C and C++ syntax given above is the canonical one used in all the textbooks - but it's difficult to read and explain. Even the above <code>typedef</code> examples use this syntax. However, every C and C++ compiler supports a more clear and concise mechanism to declare function pointers: use <code>typedef</code>, but ''don't'' store the pointer as part of the definition. Note that the only way this kind of <code>typedef</code> can actually be used is with a pointer - but that highlights the pointer-ness of it.

 

=== C and C++ ===

<source lang="C">

// This declares 'F', a function that accepts a 'char' and returns an 'int'. Definition is elsewhere.

int F(char c);

 

// This defines 'Fn', a type of function that accepts a 'char' and returns an 'int'.

typedef int Fn(char c);

 

// This defines 'fn', a variable of type pointer-to-'Fn', and assigns the address of 'F' to it.

Fn *fn = &F;      // Note '&' not required - but it highlights what is being done.

 

// This calls 'F' using 'fn', assigning the result to the variable 'a'

int a = fn('A');

 

// This defines 'Call', a function that accepts a pointer-to-'Fn', calls it, and returns the result

int Call(Fn *fn, char c) {

   return fn(c);

} // Call(fn, c)

 

// This calls function 'Call', passing in 'F' and assigning the result to 'call'

int call = Call(&F, 'A');   // Again, '&' is not required

 

// LEGACY: Note that to maintain existing code bases, the above definition style can still be used first;

// then the original type can be defined in terms of it using the new style.

 

// This defines 'PFn', a type of pointer-to-type-Fn.

typedef Fn *PFn;

 

// 'PFn' can be used wherever 'Fn *' can

PFn pfn = F;

int CallP(PFn fn, char c);

</source>

 

=== C++ ===

These examples use the above definitions. In particular, note that the above definition for <code>Fn</code> can be used in pointer-to-member-function definitions:

<source lang="CPP">

// This defines 'C', a class with similar static and member functions,

// and then creates an instance called 'c'

class C {

public:

static int Static(char c);

int Member(char c);

} c; // C

 

// This defines 'p', a pointer to 'C' and assigns the address of 'c' to it

C *p = &c;

 

// This assigns a pointer-to-'Static' to 'fn'.

// Since there is no 'this', 'Fn' is the correct type; and 'fn' can be used as above.

fn = &C::Static;

 

// This defines 'm', a pointer-to-member-of-'C' with type 'Fn',

// and assigns the address of 'C::Member' to it.

// You can read it right-to-left like all pointers:

// "'m' is a pointer to member of class 'C' of type 'Fn'"

Fn C::*m = &C::Member;

 

// This uses 'm' to call 'Member' in 'c', assigning the result to 'cA'

int cA = (c.*m)('A');

 

// This uses 'm' to call 'Member' in 'p', assigning the result to 'pA'

int pA = (p->*m)('A');

 

// This defines 'Ref', a function that accepts a reference-to-'C',

// a pointer-to-member-of-'C' of type 'Fn', and a 'char',

// calls the function and returns the result

int Ref(C &r, Fn C::*m, char c) {

   return (r.*m)(c);

} // Ref(r, m, c)

 

// This defines 'Ptr', a function that accepts a pointer-to-'C',

// a pointer-to-member-of-'C' of type 'Fn', and a 'char',

// calls the function and returns the result

int Ptr(C *p, Fn C::*m, char c) {

   return (p->*m)(c);

} // Ptr(p, m, c)

 

// LEGACY: Note that to maintain existing code bases, the above definition style can still be used first;

// then the original type can be defined in terms of it using the new style.

 

// This defines 'FnC', a type of pointer-to-member-of-class-'C' of type 'Fn'

typedef Fn C::*FnC;

 

// 'FnC' can be used wherever 'Fn C::*' can

FnC fnC = &C::Member;

int RefP(C &p, FnC m, char c);

</source>