ISO C Proposal: Function Aliases
By David R. Tribble
Revision: Draft 6, 2000-12-28
|
This document is still under construction
Document Number: WG14 N___/X3J11 __-___ C200X Revision Proposal ======================= Title: Function Aliases Author: David R. Tribble Author Affiliation: Self Postal Address: ################# Plano, TX ########## USA E-mail Address: david@tribble.com Web URL: http://david.tribble.com Telephone Number: +1 972 943 5125 (15:00-00:00 Z) +1 ############ (00:00-03:00 Z) Fax Number: +1 972 943 5111 Sponsor: ________________________________________ Revision: Draft 6 Date: 2000-12-28 Supersedes: None Proposal Category: __ Editorial change/non-normative contribution __ Correction X_ New feature __ Addition to obsolescent feature list __ Addition to Future Directions __ Other (please specify) _____________________________ Area of Standard Affected: __ Environment X_ Language __ Preprocessor __ Library __ Macro/typedef/tag name __ Function X_ Header __ Other (please specify) _____________________________ Prior Art: C++ function overloading, and C99 type-generic function macros (<tgmath.h>). Target Audience: C programmers. Related Documents (if any): None Proposal Attached: X_ Yes __ No, but what's your interest? Abstract: The addition of language syntax to provide function aliases, which are akin to C++ overloaded functions but without any of the complications to the linker model. This feature will also provide for an efficient, standard means of implementing the type-generic function macros defined in <tgmath.h>. |
There is a desire to provide a language feature similar in functionality to the function overloading feature provided in C++, i.e., a mechanism to allow for the definition of more than one function having the same identifier but different prototypes.
There is a desire to provide a language feature that allows for an efficient, standard way to implement the type-generic function macros declared in the <tgmath.h> header. These macros are essentially overloaded functions, having the same number of parameters but of different primitive types.
There is a desire to provide a method for overloading function names. This would allow the same identifier to be used for more than one function, i.e., to provide for multiple definitions of the same function, each having different parameter prototypes.
For example, a programmer might have a group of functions with similar functionality, but with different parameter types. Defining aliases for the functions, all with the same identifier, unifies them into a single group.
extern double log2_d(double x); extern float log2_d(float x); extern long log2_l(long x); _Alias double log2(double x) = log2_d; _Alias float log2(float x) = log2_f; _Alias long log2(long x) = log2_l;
These alias definitions provide set set of functions, all with the same identifier (log2), and all distiguished by their parameter types. Each alias invokes a different function, depending on the types of its parameters.
The following terms are used throughout this proposal.
The following reserved keyword [ISO § 6.4.1] is added to the language:
The following syntactic item [ISO § 6.7.1] is altered thus:
[[
Alternatively, the syntax [ISO § 6.7.4] could be altered thus:
]]
For convenience, this is shown here as being effectively the same syntax as:
A function-specifier of _Alias shall be used only in the definition of an alias.
The declarator of an alias definition specifies the name of an alias. Such a declarator shall have a (non-empty) prototype.
The initializer expression following the '=' operator in an alias definition shall evaluate to a single identifier, and is known as the designated replacement function of the alias.
The function-declarator in the alias definition must contain a non-empty prototype. The prototype specifies the signature of the alias, and also specifies the expected signature for the designated replacement function.
While requiring a prototype to appear on the alias might seem redundant,
it is advantageous for the programmer.
Without a prototype, the programmer would need to search back in the
source stream, perhaps among several header files, to find the
parameter type information for the replacement function and thus also
for the alias itself.
-End. ]
extern int qstat(void); extern double sin(double x); extern float sinf(float x); extern long double sinl(long double x); extern void pr(char *f, ...); extern int setf(int (*f)(int)); extern void setCol(enum Color col); extern int (*getf(int n))(int i); _Alias int foo(void) = qstat; // A _Alias double foo(double x) = sin; // B _Alias float foo(float a) = sinf; // C _Alias long double foo(long double d) = sinl; // D _Alias void bar(char *c, ...) = pr; // E _Alias void foo(char *s, ...) = bar; // F _Alias int foo(int (*)(int i)) = setf; // G _Alias void foo(enum Color c) = setCol; // H _Alias int (*foo(int i))(int j) = getf; // I
extern int bar(float i); extern long buz(long j); extern void bun(int c, ...); extern int fun(void); _Alias int foo(float a) = bar; // A. Designates bar(float) _Alias long foo(long a) = buz; // B. Designates buz(long) _Alias void foo(int f, ...) = bun; // C. Designates bun(int, ...) _Alias void foo(void) = fun; // D. Designates fun(void)
The return type of a function is not included in its signature. Any cv-qualifiers that directly modify the function parameters are not part of the signature. Parameter identifiers are also not part of the signature.
A function declared without a prototype (i.e., with an empty parameter list) has no parameter type information and thus has no signature, or is said to have an empty signature.
int fv(void); Signature: (void) double sin(double x); Signature: (double) float goof(const float x, enum Color c); Signature: (float, enum Color) int printf(const char fmt[const], ...); Signature: (const char *, ...) int fu(); No signature, no type information
int f(void); // A int g(void); // B. Same signature as A int h(int *ip); // C. Different signature void w(int *const); // D. Same signature as C void y(const int *q); // E. Different signature void z(int *, int); // F. Different signature void b1(int i, ...); // G. Different signature int b2(int n, ...); // H. Same signature as G void b3(int i, int j, ...); // I. Different signature void b4(long i, ...); // J. Different signature
An empty signature (i.e., a prototype with no parameter type information) is not identical with any other signature, including other empty signatures.
void f1(void); // A void f2(); // B. Empty signature, // not identical to A void f3(); // C. Empty but different signature, // not identical to A or B
In other words, two signatures are ambiguous if both match the same set of possible function call argument expression lists (ignoring the function designated by the call expression).
void g(int i, ...); // A void h(const int i, int j, ...); // B. Ambiguous with A int m(int i, long k, ...); // C. Ambiguous with A void w(long i, int k, ...); // D. Not ambiguous with A or C void y(long n, long m, ...); // E. Not ambiguous with D void z(long, long, int, ...); // F. Ambiguous with E void f(void); // G. Not ambiguous // with any signature
An empty signature (i.e., a prototype with no parameter type information) is ambiguous with any other signature, including other empty signatures.
enum color { red, green, blue }; typedef struct info info_t; static int count; extern int bar(int n); _Alias int color(int n) = f; // A. Okay _Alias int red(int n) = g; // B. Error, enum constant _Alias int info(int) = h1; // C. Okay _Alias int info(float) = h2; // D. Okay, different signature _Alias int info_t(int) = v; // E. Error, typedef _Alias int count(int) = w; // F. Error, variable _Alias void bar(float x) = z; // H. Error, function
_Alias void foo(int n) = f; // Okay _Alias void foo(int n) = foo; // Error, same name
Since parameter identifiers have prototype scope, there is no danger of the parameter names colliding with the alias name.
_Alias int foo(int foo) = bar; // Okay
extern double sin(double x); // No declaration for sinf() _Alias double foo(double x) = sin; // A. Okay _Alias float foo(float x) = sinf; // B. Error, // no such function or alias _Alias double bar(double) = foo; // C. Okay
Aliases defined outside any function block have file scope.
An alias of a given name may be defined within a block with the same signature (and possibly a different return type) as a previous alias definition of the same name in the scope outside the block. Such a definition overrides and hides the previous definition throughout the rest of the block. (Such a definition, in turn, can be overriden and hidden by a subsequent definition in yet another nested block.) Note that overriding alias definitions cannot be specified within the same scope, however.
extern float sinf(float x); extern float cosf(float x); extern float tanf(float x); _Alias float foo(float x) = sinf; // A float bar(float x) { _Alias float foo(float x) = cosf; // B if (x > 0.0) { return foo(x); // 1. Calls cosf() } else { _Alias float foo(float x) = tanf; // C return foo(x); // 2. Calls tanf() } } float baz(float x) { return foo(x); // 3. Calls sinf() }
extern _Alias double foo(double x) = bar; // A. Error static _Alias float foo(float x) = baz; // B. Error
[[ The syntax enforces this, since the _Alias keyword is a storage-class-specifier, and declarations and definitions can contain only one storage-class-specifier. ]]
This candidate set is reduced to a single function, or to none at all, by applying the appropriate function signature type information derived from the context in which the alias identifier is used. (The semantics involved in this application of type information is explained in more detail in the semantic rules that follow.)
All of the alias definitions for a given identifier comprise an unordered set of definitions.
An alias definition cannot have an empty prototype (i.e., it cannot have a signature with no parameter type information). If the only declaration in scope for the replacement function has an empty prototype (i.e., a prototype with no parameter type information), the alias definition is ill-formed.
This also implies that the designated replacement function must have been previously declared and have a declaration in scope.
extern double sin(double x); // 1 extern int f(); // 2 extern int g(enum Color c); // 3 extern int hf(enum Hue h); // 4 extern void v(int, int *); // 5 extern void sf(char *const s); // 6 // No declaration for hh() _Alias double foo(double x) = sin; // A. Okay _Alias double bar(const double) = sin; // B. Okay _Alias int bar(enum Color c) = g; // C. Okay _Alias int bar(enum Hue c) = hf; // D. Okay _Alias void foo(int a, int h[]) = v; // E. Okay _Alias void foo(char str[]) = sf; // F. Okay _Alias int foo(enum Color c) = bar; // G. Okay _Alias double foo(float x) = sin; // H. Error, signature _Alias float baz(double x) = sin; // I. Error, return type _Alias int foo(void) = f; // J. Error, signature _Alias int foo(enum Hue c) = g; // K. Error, signature _Alias void foo(int a, int b) = v; // L. Error, signature _Alias int foo() = f; // M. Error, empty signature _Alias int foo(int n) = hh; // N. Error, undefined name
This means that an alias can designate another (previously defined) alias name as its replacement function (which may in turn refer to yet another alias replacement function). The result is that both aliases have the same replacement function. (But note that a definition of a given alias name cannot designate the same name as its replacement function.)
extern int reg(int a); // A. Regular function _Alias int f(int a) = reg; // B. Replaced by reg() _Alias int g(int a) = f; // C. Replaced by reg() // Equivalent to: // _Alias int g(int a) = reg; _Alias int h(int a) = g; // D. Replaced by reg() // Equivalent to: // _Alias int h(int a) = reg;
_Alias int bar(int *) = f; _Alias int foo(int *n) = f; // A _Alias int foo(int *n) = f; // B. Okay, benign _Alias int foo(int n[]) = f; // C. Okay, benign _Alias int foo(int *const k) = f; // D. Okay, benign _Alias int foo(int *n) = bar; // E. Okay, benign _Alias void foo(int *n) = f; // F. Error, diff return type _Alias int foo(long n) = f; // G. Error, diff signature _Alias int foo(int *n) = g; // H. Error, diff replacement
_Alias void foo(char *p) = bar1; // A _Alias void foo(int n) = bar2; // B. Okay, diff signature _Alias void foo(enum Color n) = bar3; // C. Okay, diff signature _Alias void foo(const char *p) = bar4; // D. Okay, diff signature _Alias void foo(char *s) = bar5; // E. Error _Alias void foo(char s[]) = bar6; // F. Error _Alias void foo(char *const s) = bar7; // G. Error _Alias void foo(char s[const]) = bar8; // H. Error _Alias int foo(char *s) = bar9; // I. Error void bar(char *mp, const char *cp) { _Alias int foo(char *s) = bar9; // J. Okay, overrides A foo(mp); // 1. Matches A foo(cp); // 2. Matches D foo(42); // 3. Matches B }
The return type of a function or alias is not part of its signature, and is ignored for the purposes of signature matching. Thus an alias definition that differs from a previous alias definition only by its return type is an erroneous redefinition and is ill-formed.
_Alias void foo(char *p) = bar1; // A _Alias int foo(char *s) = bar9; // B. Error
_Alias int foo(int n, ...) = f; // A. Okay _Alias int foo(long n, ...) = g; // B. Okay _Alias void foo(int, int, ...) = h; // C. Error, ambiguous with A _Alias void foo(void) = w; // D. Okay _Alias int foo(long n) = v; // E. Error, ambiguous with B
If a function call expression designates the name of an alias, the rules for matching the arguments of the call to the signatures of the set of replacement functions designated by the alias are applied in the following order:
_Alias void foo(long a) = bar0; // A _Alias void foo(int a) = bar1; // B _Alias void foo(double a) = bar2; // C _Alias void foo(int a, long b) = bar3; // D _Alias void foo(long a, int b) = bar4; // E _Alias void foo(long a, long b) = bar5; // F _Alias void foo(long a, double b) = bar6; // G extern void bar(int a); // H // No declaration or definition for ghh() void bar() { foo(456L); // 1. Matches A, rule 1 foo(123); // 2. Matches B, rule 1 foo('a'); // 4. Matches B, rule 1 foo((short)4); // 3. Matches B, rule 2 foo((char)'z'); // 5. Matches B, rule 2 foo(-7.5); // 6. Matches C, rule 1 foo(-98.6f); // 7. Matches C, rule 2 foo(1, 2L); // 8. Matches D, rule 1 foo(8L, 9); // 9. Matches E, rule 1 foo(7, 1.3f); // 10. Matches G, rule 2 bar(0); // 11. Matches H, regular function foo("abc"); // 12. Error, rule 3b, no match foo(3.5, 6.7); // 13. Error, rule 3b, no match foo(4, 55); // 14. Error, rule 3c, ambiguous bar("abc"); // 15. Error, incompatible ghh(777); // 16. Error, rule 3a, undefined name }+INCOMPLETE
Any arguments in the call expression corresponding to an ellipsis parameter in the candidate replacement function are first converted according to the default type promotion rules [ISO § ?].
+INCOMPLETE
+ALSO MENTION THE RULES FOR TRAILING ELLIPSIS PARAMETER MATCHING AND DEFAULT PROMOTIONS.
In all cases, the use of an alias identifier designates the set of aliases (and by extension, the set of their replacement functions). Such a set comprises one or more candidate replacement functions.
If the set contains a single function, then the alias identifier designates that single function.
If the set contains more than one function, then the designation of the alias identifier is ambiguous, and additional type information is required in order to select a single candidate function from the set. Such additional type information is derived from the context in which the alias identifier is used.
+INCOMPLETE
In all cases, the use of an alias identifier by itself is identical to applying the '&' address-of operator to the identifier.
extern void fi(int x); extern void fd(double x); _Alias void foo(int x) = fi; // A _Alias void foo(double x) = fd; // B _Alias void bar(int x) = fi; // C extern void wee(void (*pf)(int)); extern void wef(void (*pf)(float)); extern void wey(void (*pf)()); void addresses() { void (*pfi)(int); void (*pfd)(double); float (*pff)(int); void (*pfy)(); pfi = foo; // 1. &fi pfd = foo; // 2. &fd pff = foo; // 3. Error, no match pfy = foo; // 4. Error, ambiguous pfi = &bar; // 5. &fi pfd = &bar; // 6. Error, no match pff = &bar; // 7. Error, no match pfy = &bar; // 8. &fi pfi = (void (*)(int)) bar; // 9. &fi pfd = (void (*)(double)) bar; // 10. Error, no match pfy = (void (*)()) bar; // 11. &fi pfy = (void (*)()) foo; // 12. Error, ambiguous wee(foo); // 13. wee(&fi) wef(foo); // 14. Error, no match wey(foo); // 15. Error, ambiguous if (fi == foo) // 16. &fi return; if (pfi == foo) // 17. &fd return; if (bar == foo) // 18. &fi == &fi (true) return; if (foo == foo) // 19. Error, ambiguous return; // (but true? or false?) if (bar != NULL) // 20. Unique, &fi return; if (foo != NULL) // 21. Error, ambiguous return; // (but true) if ((void (*)(int))foo != NULL) // 22. &fi return; if (foo == (void (*)(int))NULL) // 23. &fi return; } void calls(int j) { // All of these calls are equivalent: foo(j); // i. Calls fi() (foo)(j); // ii. Calls fi() (*foo)(j); // iii. Calls fi() (&foo)(j); // iv. Calls fi() (*&foo)(j); // v. Calls fi() }
_Alias void foo(char *p) = f1; // A _Alias void foo(const char *p) = f2; // B _Alias void foo(int *p) = f3; // C _Alias void foo(void *p) = f4; // D void bar(const char *cp) { int i = 2; foo("abc"); // 1. Matches A, better match than B foo(cp); // 2. Matches B exactly foo(&i); // 3. Matches C exactly foo((void *)&i); // 4. Matches D exactly foo(&cp); // 5. Matches D, call-compatible }
_Alias void foo(const char *p) = f; // A _Alias void bar(void *p) = g; // B _Alias void bar(char *p) = h; // C void buzz(int i) { foo("abc"); // 1. Matches A exactly foo((void *) "abc"); // 2. Matches A, call-compatible foo(&i); // 3. Error, no matching alias bar("xyz"); // 4. Matches C exactly, preferred over B bar((void *) "c"); // 5. Matches B exactly, preferred over C bar(&i); // 6. Matches B, call-compatible }
#include <stddef.h> _Alias void foo(char *p) = f1; // A _Alias void foo(void *p) = f2; // B _Alias void foo(long n) = f3; // C _Alias void bar(char *p) = g1; // D _Alias void bar(int n) = g2; // E _Alias void baz(char *p) = h1; // F _Alias void baz(void *p) = h2; // G void calls(char *s) { foo(s); // 1. Calls f1() foo(0); // 2. Calls f3() foo(NULL); // 3. Calls either f2() or f3() bar(s); // 4. Calls g1() bar(0); // 5. Calls g2() bar(NULL); // 6. Calls g1() or g2(), // or there is no match at all baz(s); // 7. Calls h1() baz(0); // 8. Error, ambiguous baz(NULL); // 9. Calls h2(), or is ambiguous }
Casting such NULL arguments to the appropriate pointer type disambiguates these kinds of calls.
void other_calls() { foo((char *) NULL); // 1. Calls f1() foo((int) NULL); // 2. Calls f3() foo((float *) NULL); // 3. Calls f2() bar((char *) NULL); // 4. Calls g1() bar((int) NULL); // 5. Calls g2() bar((float *) NULL); // 6. Error, no match baz((char *) NULL); // 7. Calls h1() baz((int) NULL); // 8. Error, no match baz((float *) NULL); // 9. Calls h2() }
Aliases are generally useful for defining sets of functions with similar functionality.
extern void swap_i(int *a, int *b); extern void swap_l(long *a, long *b); extern void swap_f(float *a, float *b); extern void swap_f(double *a, double *b); extern void swap_t(struct T *a, struct T *b); _Alias void swap(int *a, int *b) = swap_i; _Alias void swap(long *a, long *b) = swap_l; _Alias void swap(float *a, float *b) = swap_f; _Alias void swap(double *a, double *b) = swap_d; _Alias void swap(struct T *a, struct T *b) = swap_t;
The type-generic function macros defined in the standard <tgmath.h> header [ISO § 7.22] are ideal examples of groups of functions with similar functionality. Here is a possible implementation for them that uses aliases.
// Sample partial <tgmath.h> implementation #include <math.h> #include <complex.h> /* Functions declared: * extern double sin(double x); // A * extern float sinf(float x); * extern long double sinld(long double); * extern complex double csin(complex double x); * extern complex float csinf(complex float x); * extern complex long double csinld(complex long double x); */ // Define aliases _Alias float __tg_sin(float x) // B = sinf; _Alias complex double __tg_sin(complex double x) // C = csin; _Alias complex float __tg_sin(complex float x) // D = csinf; _Alias complex long double __tg_sin(complex long double x) // E = csinld; // Define helper functions static inline double __tg_sin_i(int x) { return sin((double) x); } static inline double __tg_sin_ui(unsigned int x) { return sin((double) x); } static inline double __tg_sin_l(long x) { return sin((double) x); } static inline double __tg_sin_ul(unsigned long x) { return sin((double) x); } static inline double __tg_sin_ll(long long x) { return sin((double) x); } static inline double __tg_sin_ull(unsigned long long x) { return sin((double) x); } // Define aliases involving default type promotions _Alias double __tg_sin(int x) = __tg_sin_i; // F _Alias double __tg_sin(unsigned int x) = __tg_sin_ui; // G _Alias double __tg_sin(long x) = __tg_sin_l; // H _Alias double __tg_sin(unsigned long x) = __tg_sin_ul; // I _Alias double __tg_sin(long long x) = __tg_sin_ll; // J _Alias double __tg_sin(unsigned long long x) = __tg_sin_ull; // K // Define type-generic function macros #define sin(x) __tg_sin(x) ...and so forth for the other type-generic macros...
The following code illustrates the use of these function macros:
#include <tgmath.h> // Use the aliases void tgcalls() { double x; complex double z; double (*f)(double x); x = sin(17.8); // 1. Matches A, calls sin() x = sin(43.6f); // 2. Matches B, calls sinf() x = sin(87); // 3. Matches F, calls sin() z = sin(19.2 + I*37.3); // 4. Matches C, calls csin() z = sin(21.0f + I*44.5f); // 5. Matches D, calls csinf() x = (sin)(32.1f); // 6. Matches A, calls sin() f = sin; // 7. Matches A, &sin }
In case 1, sin() is passed an argument of type double, which matches alias A exactly, so sin() is called.
In case 2, the argument is of type float, which matches alias B exactly, so sinf() is called.
In case 3, the argument is of type int, which matches alias F exactly, so helper function __tg_sin_i() is called, which in turn calls sin().
(The ISO rules [ISO § 7.22, paragraph 3] imply that integer arguments, i.e., arguments that are not one of the floating-point types of the appropriate library math functions, result in the function taking a float parameter to be invoked. However, the ISO example [ISO § 7.22, paragraph 7] implies that calls with integer arguments cause the regular library functions, which take parameters of type double, to be invoked. We have included code here to show one possible method for implementing such calls, which assumes the latter semantics and converts the arguments to type double.)
In case 4, the argument is of type complex double, which matches alias C exactly, so csin() is called.
In case 5, the argument is of type complex float, which matches alias D exactly, so csinf() is called.
In case 6, the identifier sin is parenthesized, so the preprocessor macro identifier is no longer visible. Thus the regular function sin() is called after converting the float argument to type double.
In case 7, the identifier sin by itself refers to the
address of function sin(),
which is then assigned to the function pointer variable f.
(There is no conflict here because the macro named sin is not
followed by a parenthesized argument list, and thus the identifier
refers only to the regular function named sin.)
Example 3
The following code is another example of using a combination of aliases and inline helper functions.
// Type-generic output functions #include <stddef.h> #include <stdio.h> static inline int output_i(long x) { return printf("%ld", x); } static inline int output_f(double x) { return printf("%lf", x); } static inline int output_p(const void *x) { return printf("%p", x); } static inline int output_s(const char *x) { return printf("%s", x); } static inline int output_s2(const char *x, size_t m) { return printf("%.*s", m, x); } static inline int output_s3(const char *x, size_t w, size_t m) { return printf("%*.*s", w, m, x); } _Alias int output(long x) = output_i; // A _Alias int output(double x) = output_f; // B _Alias int output(const void *x) = output_p; // C _Alias int output(const char *x) // D = output_s; _Alias int output(const char *x, size_t m) // E = output_s2; _Alias int output(const char *x, size_t w, size_t m) // F = output_s3;
Example uses of these aliases:
void print_some(struct Buf *bp, const char *id) { output(42); // 1. Calls output_i() output(38L); // 2. Calls output_i() output(3.14); // 3. Calls output_d() output(+6.22e+22f); // 4. Calls output_d() output(bp); // 5. Calls output_p() output("abc"); // 6. Calls output_s() output(id, 8); // 7. Calls output_s2() output(bp->name, 8, sizeof(bp->name)); // 8. Calls output_s3() }
Aliases can be overloaded such that different numbers of arguments in function call expressions cause different functions to be invoked.
// Declare functions extern int sys_open(const char *fn, int mode); extern int sys_create(const char *fn, int mode, int perm); // Define aliases _Alias int openf(const char *fn, int m) // A = sys_open; _Alias int openf(const char *fn, int m, int p) // B = sys_create; // Use the aliases void test(const char *name, int mode, int perm) { int fd; fd = openf(name, mode); // 1. Calls sys_open() fd = openf(name, mode, perm); // 2. Calls sys_create() }
Aliases can be combined with inline functions to produce interesting replacements, such as argument reordering and the supplying of default argument values.
extern int sysfunc(int op, void *a, void *b); static inline int syscall_readb(char *buf, int len) { // Provide default argument values and argument reordering return sysfunc(SYS_READ, &len, buf); } static inline int syscall_read1(char *buf) { int len = 1; // Provide default argument values and argument reordering return sysfunc(SYS_READ, &len, buf); } static inline int syscall_readc(void) { int len = 1; char ch; // Provide default argument values and argument reordering sysfunc(SYS_READ, &len, &ch); return ch; } _Alias int read(char *buf, int len) = syscall_readb; _Alias int read(char *buf) = syscall_read1; _Alias int read(void) = syscall_readc;
This is another example of using aliases and inline forwarding functions to simulate default arguments.
extern Rect * makeRect(int x, int y, int width, int height); // A static inline Rect * makeRect2(int w, int h) { // Provide default X and Y coordinate values return makeRect(0, 0, w, h); } _Alias Rect * makeRect(int w, int h) = makeRect2; // B void make(int w, int h) { Rect * r; r = makeRect(10, 20, w, h); // 1. Okay r = makeRect(w, h); // 2. Okay, default X and Y values r = makeRect(17, w, h); // 3. Error, no match r = makeRect(w); // 4. Error, no match r = makeRect(); // 5. Error, no match }
An appropriate combination of aliases and forwarding inline functions can be used to provide functions with return types or parameter types with more restrictive cv-qualifiers.
extern char * strchr(const char *s, int c); extern void free(void *p); // Define inline helper functions static inline const char * my_strchr_c(const char *s, int c) { // Return type is converted return strchr(s, c); } static inline void my_freec(const char *p) { // Argument type is converted return free((void *) p); } // Define aliases _Alias const char * my_strchr(const char *s, int c) // A = my_strchr_c; _Alias void my_free(const char *p) = my_freec; // B // Use the aliases void f(char *s) { const char * cp = s; cp = my_strchr(s, 'a'); // 1. Okay cp = my_strchr(cp, 'b'); // 2. Okay s = my_strchr(s, 'c'); // 3. Error s = my_strchr(cp, 'd'); // 4. Error my_free(s); // 5. Okay my_free(cp); // 6. Okay }
Aliases can be defined having the same name as existing regular functions by using separately compiled translation units and extern helper functions.
//---------------------------------------- // mysin.c extern double sin(double x); double sin_helper(double n) { return sin(x); } //---------------------------------------- // mysin.h extern double sin_helper(double x); extern float sinf(float x); _Alias double sin(double x) = sin_helper; // A _Alias float sin(float x) = sinf; // B //---------------------------------------- // code.c #include "mysin.h" void calls() { double (*pf)(double); sin(1.50); // 1. Calls sin_helper(), which calls sin() sin(3.14f); // 2. Calls sinf() (sin)(6.28f); // 3. Calls sinf(), not sin() pf = sin; // 4. &sin_helper pf = (sin); // 5. &sin_helper }
As in the previous example, an alias can be defined having the same name as an existing regular function by using a preprocessor macro to hide the regular function name. (This method does not require separately compiled files like the previous example.)
//---------------------------------------- // mystrchr.h #include <string.h> /* Functions declared: * extern char * strchr(const char *s, int c); // A */ static inline char * strchr_nc(char *s, int c) { return strchr(s, c); } static inline const char * strchr_c(const char *s, int c) { return strchr(s, c); } _Alias char * xstrchr(char *s, int c) = strchr_nc; // B _Alias const char * xstrchr(const char *s, int c) = strchr_c; // C #define strchr xstrchr //---------------------------------------- // code.c #include <string.h> #include "mystrchr.h" void calls(char *st) { const char * cp = st; char * (*pf)(char *, int); st = strchr(st, 'a'); // 1. Calls strchr_nc() cp = strchr(cp, 'b'); // 2. Calls strchr_c() cp = strchr(st, 'c'); // 3. Calls strchr_nc() st = strchr(cp, 'd'); // 4. Error, loss of const cp = (strchr)(cp, 'e'); // 5. Calls strchr_c() pf = strchr; // 6. &strchr_nc pf = (strchr); // 7. &strchr_nc }
If we use the following directive instead:
#define strchr(...) xstrchr(__VA_ARGS__)
then the last cases have slightly different results:
cp = (strchr)(cp, 'e'); // 5b. Calls strchr() directly pf = strchr; // 6b. &strchr pf = (strchr); // 7b. &strchr
For example, given:
extern int bar(int n); _Alias int foo(int n) = bar; int baz(int i) { return foo(i); }
The call to alias foo() could be implemented by simply replacing it with a direct call to bar():
int baz(int i) { return bar(n); }
The call to foo() could also be implemented as a call to a hidden static function which invokes bar():
static int __local_foo(int n) { return bar(n); } int baz(int i) { return __local_foo(i); }
_Alias int foo(const char *s) = f1; // A _Alias int foo(char *s) = f2; // B _Alias int bar(const void *s) = g1; // C _Alias int bar(void *s) = g2; // D _Alias int baz(const char *s, const char *t) = h1; // E _Alias int baz(char *s, char *t) = h2; // F void scalls() { foo("abc"); // 1. Calls f2(), not f1() foo((const char *) "abc"); // 2. Calls f1() bar("xyz"); // 3. Calls g2(), not g1() bar((const char *) "xyz"); // 4. Calls g1() baz("p", "q"); // 5. Calls h2() baz((const char *) "p", "q"); // 6. Error, ambiguous baz("p", (const char *) "q"); // 7. Error, ambiguous baz((const char *) "p", (const char *) "q"); // 8. Calls h2() }
(It would be more logical, and more consistent with C++ semantics, if string constants had type 'const char[n]', but this would require a change to existing C semantics that would probably break a fair number of existing programs.)
// This compiles as both C and C++ code static inline void foo_helper1(int n) { // The actual work for foo(int) is performed here ...function body... } static inline int foo_helper2(float n) { // The actual work for foo(float) is performed here ...function body... } #ifdef __cplusplus // C++ void foo(int n) { foo_helper1(n); } // A-1 int foo(float n) { return foo_helper2(n); } // B-1 #else // C _Alias void foo(int n) = foo_helper1; // A-2 _Alias int foo(float n) = foo_helper2; // B-2 #endif
Calling the overloaded functions is done the same way in both languages:
void use() { foo(42); // 1. Calls foo(int), which calls foo_helper1() foo(3.4f); // 2. Calls foo(float), which calls foo_helper2() }
extern double foo(double x); extern float bar(float x); extern double baz(double x); extern double ind(double (*func)(double x)); #if DEFINE_ALIASES _Alias float foo(float x) = bar; #endif void works() { double x; double (*pf)(double x); x = foo(1.5); x = foo(2.6f); x = foo(579); pf = foo; pf = &foo; x = (*pf)(7.8); x = ind(foo); if (foo == baz) return; if (foo == NULL) return; }
The type-generic function macros defined in the standard <tgmath.h> header are effectively overloaded function names. It is unspecified as to what mechanism or syntax is used to support these macros, however. Presumably, they are defined with special identifiers or operators known to the compiler, e.g.:
// Theoretical <tgmath.h> implementation // Note that the '__tg_xxx' names are special identifiers known // to the compiler. #define cos(x) __tg_cos(x) #define sin(x) __tg_sin(x) #define tan(x) __tg_tan(x) ...etc...Or perhaps:
// Theoretical <tgmath.h> implementation // Note that '__typeof()' is a special compiler operator extern double __tg_sin_double(double); extern float __tg_sin_float(float); extern long double __tg_sin_long_double(long double); ...etc... #define __tg_splice(a,b) a##b #define cos(x) __tg_splice(__tg_cos_, __typeof(x)) #define sin(x) __tg_splice(__tg_sin_, __typeof(x)) #define tan(x) __tg_splice(__tg_tan_, __typeof(x)) ...etc...
The C++ language provides function overloading. It also supports function templates, which provide another method for overloading functions.
// C++ code extern int foo(); // A, same as foo(void) extern int foo(int x); // B extern double foo(double x); // C template <class Type> // D Type foo(Type *x) { ... } void bar(Dat *p) { foo(); // 1. Calls A, foo() foo(123); // 2. Calls B, foo(int) foo('a'); // 3. Calls B, foo(int) foo(1.5); // 4. Calls C, foo(double) foo(2.8f); // 5. Calls C, foo(double) foo(p); // 6. Calls D, foo<Dat>(Dat*) }
Overloaded functions are distinguished by their signatures, which is essentially their prototypes. A function's return type is not part of its signature.
C++ employs fairly complicated rules for finding the best match for a given call expression from the set of all possible matching functions. Argument promotions and implicit type conversions are considered, as are possible template function instantiations.
Function name overloading and template function instantiation are accomplished in C++ by allowing the compiler to apply name mangling to function names to produce unique linker symbols. Such mangling typically involves combining and mapping the function's name, namespace, class prefix, template parameters, and prototype argument types into a single symbolic identifier. For this reason, C++ reserves all identifiers containing two consecutive underscores (e.g., foo__bar), so that compilers may have at least one symbolic namespace into which they can transfrom overloaded function names.
For example, a C++ compiler might mangle the names of the following overloaded functions as shown:
True function overloading and function templates are different from function aliases. Overloaded and template function names are mapped into actual symbolic linker identifiers. In contrast, function aliasing simply replaces overloaded function names with calls to regular functions, so no name mangling is performed or necessary.
Some issues have been resolved.
Upon closer examination, however, it was discovered that this was unneccesary, due to the fact that the type-generic functions are macros, and thus can be #defined to any name the implementor chooses.
Consequently, a semantic rule was added to disallow aliases from having the same name as any previously defined function. This simplified the rules for determining ill-formed alias definitions, and also simplified the matching rules for call expressions.
There is a technique for getting around this restriction, however, which is demonstrated in the Examples section.
If this is allowed, the semantics of these situations must be resolved:
These situations are more complicated if the alias has the same name as a regular function. (In such situations, the best approach might be to treat the regular function as a member of the candidate alias set for the purposes of finding an appropriate match.)
extern int bar(int a, int b, int c); _Alias int foo(int x, int y = 0, int z = 0) = bar;is equivalent to:
inline int foo_helper1(int x, int y) { return bar(x, y, 0); } inline int foo_helper2(int x) { return bar(x, 0, 0); } _Alias int foo(int x, int y, int z) = bar; _Alias int foo(int x, int y) = foo_helper1; _Alias int foo(int x) = foo_helper2;
In other words, we allow the trailing parameters in an alias definition to be initialized with default constant expressions. Thus a given alias definition with N parameters, where the last M (0 <= M <= N) parameters have default values specified, is equivalent to defining M+1 aliases of the same name.
The rules for matching call expressions to alias candidate sets are the same.
foo(1); // Calls bar(1, 0, 0) foo(1, 2); // Calls bar(1, 2, 0) foo(1, 2, 3); // Calls bar(1, 2, 3)
We would, of course, have to add semantic rules to disallow duplicate (ambiguous) alias definitions.
Note that once an alias is defined with a default value for its first parameter, this limits the remaining alias definitions possible for the same identifier. Consider:
_Alias int a(int = 0, int = 0) = f1; // A _Alias long a(int i) = f2; // B. Error, duplicate _Alias long a(void) = f3; // C. Error, ambiguous _Alias void a(long n) = f4; // D. Okay _Alias void a(char *) = f5; // E. Okay _Alias void a(char * = "zz") = f6; // F. Error, ambiguous _Alias float a(float x, ...) = f7; // G. Okay ... a(1, 2, 3); // 1. Calls f1(1, 2, 3) ... a(1, 2); // 2. Calls f1(1, 2, 0) ... a(1); // 3. Calls f1(1, 0, 0) ... a(); // 4. Calls f1(0, 0, 0) ... a(42L); // 5. Calls f4(42L) ... a("abc"); // 6. Calls f5("abc") ... a(1.5f, -3); // 7. Calls f7(1.5f, -3)
It should be noted, however, that any definition of an alias with default arguments can be simulated with the appropriate combination of alias definitions and inline helper functions. In fact, aliases with more complicated default parameter permutations can be written using inline functions than what is allowed by the semantic rules given above. (See the Examples section for specific examples of this technique.) So it appears that it is not worth the effort (i.e., it may not be worth the cost of changes to the existing syntax and semantics of function prototype declarations) to include default alias arguments in this proposal.
extern char * bar1(int n); extern char * bar2(void); _Alias const char * foo(int n) = bar1; _Alias volatile char * foo(void) = bar2;
This effectively casts the return value of the replacement function, applying the additional cv-qualifiers to it. The same effect can be achieved using inline helper functions, but with more code:
inline const char * bar1_helper(int n) { return bar1(n); } inline volatile char * bar2_helper(void) { return bar2(); } _Alias const char * foo(int n) = bar1_helper; _Alias volatile char * foo(void) = bar2_helper;
This capability can be used to add more type safety to existing functions. Consider:
extern char * strchr(const char *s, int c); _Alias const char * sfind(const char *s, int c) = strchr; void bar(char *buf, char c) { const char * res; res = strchr(buf, c); // 1. Okay res = sfind(buf, c); // 2. Okay buf = strchr(buf, c); // 3. Okay, but const removed buf = sfind(buf, c); // 4. Error, const removed }
We would not allow the reverse, i.e., defining aliases to have less restrictive cv-qualifiers than their designated replacement functions.
extern const char * baz1(int n); extern volatile char * baz2(void); _Alias char * foo(int n) = baz1; // Error _Alias char * foo(void) = baz2; // Error
It should be noted, however, that this capability can be simulated with the appropriate combination of alias definitions and inline helper functions. (See the Examples section for specific examples of this technique.) So it appears that it is not worth the effort (i.e., it may not be worth the cost of changes to the existing syntax and semantics of function prototype declarations) to provide for different return types.
extern int (*pif)(int n); _Alias int foo(int n) = pif; void baz() { pif = &bar; // 'pif' now points to a function foo(123); // Calls (*pif)(123), which calls bar(123) }
This could imply, though, that the replacement initializer can be an arbitrary expression, which is something we prefer to avoid in this proposal. For example:
struct FuncPtr { double (*pf[10])(int n); }; extern struct FuncPtr st; _Alias double foo(int n) = st.pf[3]; ... foo(7); // Calls (*st.pf[3])(7)
Even more complicated examples are possible if arbitrary replacement expressions are allowed and aliases are allowed to have block scope:
extern void (*afp[10])(int); void bar(int i) { alias void foo(int) = afp[i]; foo(123); // Calls (*afp[i])(123) }
Note that function pointer expressions such as the ones shown above are not actually constant expressions; in fact, any expression involving the value of a variable is not a constant expression whose value is known at compile time. (Expressions involving the addresses of variables are constants known at compile time, though, e.g., &st.i.) Function identifiers (and by extension, alias identifiers), on the other hand, are constant expressions.
It should be noted, however, that the capability of defining an alias to be replaced by a function pointer can be simulated with the appropriate combination of alias definitions and inline helper functions. For example:
extern int (*pif)(int n); static inline int pif_call(int n) { return (*pif)(n); } _Alias int foo(int n) = pif_call; void baz() { pif = &bar; // 'pif' now points to a function foo(123); // Calls (*pif)(123), which calls bar(123) }
So it appears that it is not worth the effort to provide for non-constant expressions as alias replacement initializers.
extern int printf(const char *fmt, ...); extern int fprintf(FILE *fp, const char *fmt, ...); extern int sprintf(char *s, const char *fmt, ...); _Alias int print(const char *fmt, ...); // A _Alias int print(FILE *fp, const char *fmt, ...); // B _Alias int print(char *s, const char *fmt, ...); // C void bar(char *buf) { print("Hello"); // 1. Calls A, printf() print("%d %d", 2, 3); // 2. Calls A, printf() print(stdout, "you"); // 3. Calls B, fprintf() print(buf, "great"); // 4. Error, ambiguous best match print("%s", "foo"); // 5. Error, ambiguous best match }
Alias C is ambiguous with alias A (but not quite entirely, due to the const qualifier).
(See the Examples section for another technique for writing overloaded output functions.)
There remain a few unresolved issues.
We prefer not to confuse function aliasing in C with function overloading in C++, so keywords like overload are not desirable.
The historical approach to creating new keywords for ISO C has been to use a spelling scheme such as _Alias, so as to place the new keyword into the reserved namespace and keep it out of the programmer's namespace. The drawback to this approach is that such a keyword looks aesthetically unpleasing.
Some of the new C99 keywords (e.g., inline and restrict) are already in all-lowercase form, while the other new keywords (e.g., _Bool and _Complex) have standard headers that provide preprocessor macros defined as more aesthetically pleasing equivalents (e.g., bool and complex). It is not clear whether such a macro definition should exist for _Alias/alias, or where it would be located, or if the creation of an entirely new standard header file would be appropriate.
_Alias foo = double bar(double x);This is almost the same as the syntax shown in this proposal, except that the prototype and return type information occurs in the designated replacement function portion of the alias definition. The semantics are the same.
_Alias foo = bar;This syntax is brief, and is technically all that is required to define a alias. The drawback is that it conveys too little information. Specifically, it does not indicate the signature or return type information for the alias. This would probably be annoying to programmers, who would have to search back through the source stream, perhaps covering many header files, to find the type information for the replacement function and thus for the alias itself. It would probably also be a potential source of errors (i.e., defining an alias for the wrong replacement function which just happens to have the same signature and return type as the intended replacement function). It is also potentially confusing, in that the meanings of the identifiers on either side of the '=' sign might be mixed up.
_Alias double foo(double x) = expression;This syntax allows an alias to be replaced by an arbitrary expression. Several questions must be resolved, however, such as, must the expression be a valid expression at the point of the alias definition or at the point that a call expression is replaced by an alias's replacement expression? Also, is the expression simply a series of tokens, or must it be a full syntactically valid expression? (The former breaks one of the design constraints.)
_Alias foo = { sin, sinf, sinld };This syntax designates a list of replacement functions in a given alias definition. Unresolved are the questions of:
_Alias foo = { double sin(double x); float sinf(float x); long double sinld(long double x); };This syntax designates a list of replacement functions in a given alias definition (similar to syntax 4). The primary difference is that the designated replacement functions must be specified with return types and parameter prototypes; any mismatched between the definitions specified and the declarations in scope for any replacement function results in an error.
Unresolved are the questions of:
Type foo<>(double x) = bar;This resembles the template function syntax of C++. The '<>' suffix on the function identifier serves to indicate that it is an alias definition instead of a regular function declaration. The advantage to a syntax like this is that no new keyword is required. The disadvantage is that it is not obvious from this syntax that "alias definition" is the intended meaning.
_Alias double foo(double x) { function-body }This syntax makes aliases almost identical to inline functions. The problem is that aliases and inline functions have different semantics, most especially in the fact that aliases are not real functions (which are addressable and can be extern), whereas inline functions are.
inline foo = bar;This extends the current syntax for inline functions. The problem with this is that inline functions and aliases have rather different semantics (i.e., inline functions can be extern and are addressable, whereas aliases are not but are overloadable), and it might be confusing to use the same keyword for both.
double foo(double x) = bar;This is almost the same as the syntax shown in this proposal, except that there is no _Alias keyword is used. This differs syntactically from a function definition by the presence of the '=' operator. The alias semantics are the same.
double foo.bar(double x);This specifies an alias named foo with a designated replacement function named bar. No _Alias keyword is necessary. It might be hard to remember which identifier is the alias and which is the replacement function, however.
void (*fp)(int k); fp = (_Alias void (*)(int)) foo; // A. Fails if foo is not // of type 'void (*)(int)' fp = (_Alias (*)(int)) foo; // B. Same as A, without // the return type fp = (_Alias (int)) foo; // C. Same as B, without // the '(*)' portionThis is safer than the existing cast syntax, which might silently cast a unique function address to the wrong type:
fp = (void (*)(int)) foo; // Silently casts foo to type // 'void (*)(int)', whether it // is that type or not
_Alias int foo(int n) = { bar1 }; _Alias int foo(float n) = { &bar2 };
But should this be allowed, or specifically disallowed (since the net result of the bracketed initializer is the same as if the replacement function identifier had been specified without the surrounding brackets)? The current rules do not specifically disallow this form of initializer.
_Alias int foo(int n) = ((&bar));
But should this be allowed, or should we restrict the replacement expression to being only a function or alias identifier? (This is not a critical issue, since the variety of constant initializer expressions possible in this context are quite limited, since they all have to evaluate (at compile time) to a function identifier.)
_Alias int foo(int n) = f1; // A. First definition for 'foo' _Alias void foo(float x) += f2; // B. Subsequent definition _Alias int foo(void) += f3; // C. Subsequent definition _Alias foo; // D. Last definition for 'foo'
The first definition A for a given alias identifier would specify its designated replacement function using the '=' operator. Such a definition is ill-formed if any alias definitions for that name appeared previously in the source stream.
The second and subsequent definitions (B and C) for the alias identifier would specify their designated replacement function using the '+=' operator. It is not an error for such a definition to be the first definition for a given alias name, i.e., such definitions do not need to be preceded by a "first" alias definition that specifies the '=' operator.
The last definition for a given alias is followed by alias definition D, which specifies that no more definitions for the given identifier are allowed. Any subsequent alias definitions for that identifier are ill-formed.
Neither definitions of the form at A or D are required, but specifying them provides the programmer the ability to control the entire set of definitions for a given alias.
The exact syntax that would ultimately be used for this capability is not important; the syntax shown above is merely a suggested syntax. Other possible syntaxes are:
_Alias foo = { int f1(int n); void f2(float x); int f3(void); };
_Alias foo = { sin, sinf, sinl, sinld };
_Alias foo = { _Alias int foo(void) = bar1; _Alias int foo(int) = bar2; };
_Alias int foo(void) = bar1; _Alias int foo(int) = bar2; _Alias const foo;
The drawback of syntaxes (1), (2), and (3) are that they require all of the definitions for a given alias name to appear together in a single place within the source stream, which might be inconvenient if it is desired to spread the definitions among several header files.
It is not clear whether preventing the programmer from adding definitions to an existing alias set is a good thing or not.
_Alias int foo(int) = f1; // A _Alias int foo(float) = f2; // B void g() { _Alias foo; // C. Forget A and B, // start a new alias set _Alias void foo(int) = f3; // D _Alias void foo(long) = f4; // E foo(1); // 1. Calls f3() foo(2L); // 2. Calls f4() foo(3.0f); // 3. Error, no match } void h() { foo(1); // 4. Calls f1() foo(2L); // 5. Calls f2() foo(3.0f); // 6. Calls f2() }
Definition C forces a new empty alias set in its scope, forgetting any previous definitions of alias foo(). Following C, the definitions D and E comprise the entire set of aliases visible in the rest of the block (in the body of g()).
extern <unknown-type> bar(<unknown-prototype>); _Alias foo = bar;
Such a definition defines an alias named foo() that has the same signature and return type as function bar(), but the exact types of these are not specified (and presumably are not important).
_Alias int foo(int n) = f1; // A _Alias void foo(float n) = f2; // B _Alias int foo(void) = f3; // C _Alias bar = foo; // D _Alias int foo(double) = f4; // E
In this example, we could stipulate that definition D creates three (additional) aliases named bar() with the same signatures and return types as the three aliases named foo(). In other words, such a definition copies the previous alias definitions, giving the copied aliases a new name. Definition E adds a new alias to the set named 'foo()', but does not affect the set named 'bar()'. Thus the example above is equivalent to:
_Alias int foo(int n) = f1; // A _Alias void foo(float n) = f2; // B _Alias int foo(void) = f3; // C _Alias int bar(int n) = f1; // D1 _Alias void bar(float n) = f2; // D2 _Alias int bar(void) = f3; // D3 _Alias int foo(double) = f4; // E
It would be an error if any of the alias definitions being copied overrides a previous definition in the same scope of an alias of the same name and signature but with a different return type or replacement function.
// Definitions A, B, C same as above _Alias char ali(int j) = a4; // F _Alias ali = foo; // G, Error
The alias copying definition at G is ill-formed because the alias set named foo() contains a definition that conflicts with a previous definition for alias ali(). Such an overriding redefinition is ill-formed.
... foo.(int) ... // Selects foo(int) from the alias setThis could be used in cases such as:
_Alias int foo(int n) = f1; // A _Alias int foo(double n) = f2; // B void h() { int (*pf)(int); int (*pd)(double); pf = foo.(int); // 1. Selects A, &f1 (*pf)(1); // Calls f1() pd = foo.(double); // 2. Selects B, &f2 (*pf)(2.0); // Calls f2() foo.(int)(3); // 3. Selects A, calls f1() }
There is the additional possibility that this syntax could be used to select the best matching call-compatible function from the alias set if no exact match exists. For example:
foo.(float)(4.0f); // 4. Selects B, calls f2()
Such an expression could select B (if this is deemed acceptable semantics) because it is the best matching call-compatible replacement function in the alias set foo(). The drawback to this is that the resulting selected function cannot be used in any context except as the designated function in a call expression. In particular, the selected function's address could not be assigned to a function pointer variable, because its signature and return type are not known, or are not identical to those of the function pointer variable.
In any case, except for the last possibility, the capability already exists in the current proposal for selecting a matching replacement function from an alias set, using an explicit cast to a specific function pointer type:
pf = (int (*)(int)) foo; // 1b. Selects A, &f1 pd = (int (*)(double)) foo; // 2b. Selects B, &f2 ((int (*)(int)) foo)(3); // 3b. Selects A, calls f1()
It could be argued, though, that like case (4) above, it might be useful to allow an explicit cast to select the best matching call-compatible replacement function if an exact match does not exist:
((int (*)(float)) foo)(4.0f); // 4b. Selects B, calls f2()
This particular situation could be specified using special cast expression syntax:
((_Alias (float)) foo)(4.0f); // 4c. Selects B, calls f2()This syntax allows a given prototype (signature) to be specified in order to select the best matching replacement function from the alias set. As mentioned above for case (4), the drawback to this syntax is that the selected function could only be used as the designated function in a function call expression, and could not be assigned to a function pointer variable or passed to a function as an argument of function pointer type.
struct Foo; extern struct Foo * foo_open(int n); extern int foo_read(const struct Foo *this, int n); extern void foo_close(struct Foo *this); struct Foo { ... _Alias struct Foo * open(int n) static? = foo_open; _Alias int read(int i) = foo_read; _Alias void close(void) = foo_close; }; void h() { struct Foo * b; b = b?->open(3); // Calls foo_open(3) b->read(42); // Calls foo_read(&b, 42) b->close(); // Calls foo_close(&b) }
Unresolved are questions like:
a. what would the exact syntax be;
b. what would be the relationship between the structure type and its
member function aliases;
c. is there an equivalent to a static member function
(i.e., a member function that does not take an
implicit this structure pointer as its first parameter).
This suggestion might be stretching the expected capabilities of aliases a bit too far. It is perhaps a wiser approach to consider features like this only after the basic function aliasing feature has been accepted and some experience with using it has been gained by programmers over a reasonable amount of time.
Most of the discussion and initial design for function aliasing began as a series of postings on the comp.std.c newsgroup.
The following people were especially helpful in providing inspiration and feedback for this proposal:
This page has been accessed [] times since 2000-11-10 (Draft 1).