Date: Tue, 1 Oct 2019 21:25:51 +1000
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>
(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>
Received on 2019-10-01 06:28:04