Inline (embedded) functions. Function overloading

Function overloading

Overloading of operations (operators, functions, procedures)- in programming - one of the ways to implement polymorphism, which consists in the possibility of simultaneous existence in one scope of several various options operations (statement, function, or procedure) that have the same name but differ in the types of parameters they apply to.

Terminology

The term "overloading" is a tracing paper of the English "overloading", which appeared in Russian translations of books on programming languages ​​in the first half of the 1990s. Perhaps this is not the most the best option translation, since the word "overload" in the Russian language has an established meaning of its own, radically different from the newly proposed one, however, it has taken root and is quite widespread. In the publications of the Soviet era, similar mechanisms were called in Russian “redefinition” or “redefinition” of operations, but this option is not disputable: discrepancies and confusion arise in the translations of the English “override”, “overload” and “redefine”.

Reasons for the appearance

Most early programming languages ​​had a restriction that no more than one operation with the same name could be available in a program at the same time. Accordingly, all functions and procedures visible at a given point in the program must have different names. The names and designations of functions, procedures, and operators that are part of the programming language cannot be used by the programmer to name his own functions, procedures, and operators. In some cases, a programmer can create his own program object with the name of another already existing one, but then the newly created object “overlaps” the previous one, and it becomes impossible to use both options at the same time.

This situation is inconvenient in some fairly common cases.

  • Sometimes there is a need to describe and apply operations to data types created by the programmer that are equivalent in meaning to those already available in the language. A classic example is a library for working with complex numbers. They, like ordinary numeric types, support arithmetic operations, and it would be natural to create for of this type operations "plus", "minus", "multiply", "divide", denoting them with the same operation signs as for other numeric types. The ban on the use of elements defined in the language forces the creation of many functions with names like ComplexPlusComplex, IntegerPlusComplex, ComplexMinusFloat, and so on.
  • When operations of the same meaning are applied to operands various types, they have to be named differently. Inability to apply for different types functions with the same name leads to the need to invent different names for the same thing, which creates confusion, and can lead to errors. For example, in the classical C language, there are two versions of the standard library function for finding the modulus of a number: abs() and fabs() - the first is intended for an integer argument, the second for a real one. This situation, combined with weak C type checking, can lead to a hard-to-find error: if a programmer writes abs(x) in the calculation, where x is a real variable, then some compilers will generate code without warning that will convert x to an integer by discarding the fractional parts and calculate the modulus from the resulting integer!

In part, the problem is solved by means of object programming - when new data types are declared as classes, operations on them can be formalized as class methods, including class methods of the same name (since methods of different classes do not have to have different names), but, firstly, such a design way of operations on values ​​of different types is inconvenient, and secondly, it does not solve the problem of creating new operators.

Operator overloading itself is just "syntactic sugar", although even as such it can be useful because it allows the developer to program in a more natural way and makes custom types behave more like built-in ones. If we approach the issue from a more general position, then we can see that the tools that allow you to expand the language, supplement it with new operations and syntactic constructions (and overloading of operations is one of such tools, along with objects, macros, functionals, closures) turn it already in the metalanguage - a means of describing languages ​​oriented to specific tasks. With its help, it is possible to build a language extension for each specific task that is most appropriate for it, which will allow describing its solution in the most natural, understandable and simple form. For example, in an application to overloading operations: creating a library of complex mathematical types (vectors, matrices) and describing operations with them in a natural, “mathematical” form, creates a “language for vector operations”, in which the complexity of calculations is hidden, and it is possible to describe the solution of problems in terms of vector and matrix operations, focusing on the essence of the problem, not on the technique. It was for these reasons that such means were once included in the Algol-68 language.

Overload mechanism

Implementation

Operator overloading involves the introduction of two interrelated features into the language: the ability to declare several procedures or functions with the same name in the same scope, and the ability to describe your own implementations of operations (that is, the signs of operations, usually written in infix notation, between operands). Basically, their implementation is quite simple:

  • To allow the existence of several operations of the same name, it is enough to introduce a rule into the language, according to which an operation (procedure, function, or operator) is recognized by the compiler not only by name (notation), but also by the types of their parameters. So abs(i), where i is declared as an integer, and abs(x), where x is declared as real, are two different operations. Fundamentally, there are no difficulties in providing just such an interpretation.
  • To enable defining and redefining operations, it is necessary to introduce appropriate syntactic constructions into the language. There can be quite a lot of options, but in fact they do not differ from each other, it is enough to remember that the entry of the form “<операнд1> <знакОперации> <операнд2>» is fundamentally similar to calling the function «<знакОперации>(<операнд1>,<операнд2>)". It is enough to allow the programmer to describe the behavior of operators in the form of functions - and the problem of description is solved.

Options and problems

Overloading procedures and functions at the level common idea, as a rule, is not difficult either to implement or to understand. However, even in it there are some "pitfalls" that must be considered. Allowing operator overloading creates a lot more problems for both the language implementer and the programmer working in that language.

Identification problem

The first question that a developer of a language translator that allows overloading of procedures and functions faces is how to choose from among the procedures of the same name the one that should be applied in a given specific case? Everything is fine if there is a variant of the procedure, the types of formal parameters of which exactly match the types of the actual parameters used in this call. However, in almost all languages, there is some degree of freedom in the use of types, assuming that the compiler automatically performs type-safe conversions in certain situations. For example, in arithmetic operations on real and integer arguments, integer is usually converted to real type automatically, and the result is real. Suppose there are two variants of the add function:

int add(int a1, int a2); float add(float a1, float a2);

How should the compiler handle the expression y = add(x, i) where x is a float and i is an int? Obviously there is no exact match. There are two options: either y=add_int((int)x,i) , or as y=add_flt(x, (float)i) (here the names add_int and add_float denote the first and second versions of the function, respectively).

The question arises: should the compiler allow this use of overloaded functions, and if so, on what basis will it choose the particular variant used? In particular, in the example above, should the translator consider the type of the variable y when choosing? It should be noted that the above situation is the simplest, much more complicated cases are possible, which are aggravated by the fact that not only built-in types can be converted according to the rules of the language, but also classes declared by the programmer, if they have kinship relations, can be cast one to another. There are two solutions to this problem:

  • Prohibit inaccurate identification at all. Require that for each particular pair of types there is an exactly suitable variant of the overloaded procedure or operation. If there is no such option, the compiler should throw an error. The programmer in this case must apply an explicit conversion to cast the actual parameters to the desired set of types. This approach is inconvenient in languages ​​such as C++, which allow a fair amount of freedom in dealing with types, since it leads to a significant difference in the behavior of built-in and overloaded operators (arithmetic operations can be applied to ordinary numbers without thinking, but to other types - only with explicit conversion) or to the emergence of a huge number of options for operations.
  • Establish certain rules for choosing the “nearest fit”. Usually, in this variant, the compiler chooses those of the variants whose calls can be obtained from the source only by safe (non-lossy information) type conversions, and if there are several of them, it can choose based on which variant requires fewer such conversions. If the result leaves more than one possibility, the compiler throws an error and requires the programmer to explicitly specify the variant.

Operation Overloading Specific Considerations

Unlike procedures and functions, infix operations of programming languages ​​have two additional properties that significantly affect their functionality: priority and associativity, the presence of which is due to the possibility of "chain" recording of operators (how to understand a + b * c: as (a + b )*c or like a+(b*c) ? The expression a-b+c is (a-b)+c or a-(b+c) ?).

The operations built into the language always have predefined traditional precedence and associativity. The question arises: what priorities and associativity will the redefined versions of these operations have, or, moreover, the new operations created by the programmer? There are other subtleties that may require clarification. For example, in C there are two forms of increment and decrement operators ++ and -- - prefix and postfix, which behave differently. How should the overloaded versions of such operators behave?

Different languages ​​deal with these issues in different ways. Thus, in C++, the precedence and associativity of overloaded versions of operators is kept the same as those defined in the language; it is possible to separately overload the prefix and postfix forms of the increment and decrement operators using special signatures:

So int is used to make a difference in signatures

Announcement of new operations

The situation with the announcement of new operations is even more complicated. Including the possibility of such a declaration in the language is not difficult, but its implementation is fraught with significant difficulties. Declaring a new operation is, in fact, creating a new keyword programming language, complicated by the fact that operations in the text, as a rule, can follow without separators with other tokens. When they appear, additional difficulties arise in the organization of the lexical analyzer. For example, if the language already has the operations “+” and the unary “-” (change of sign), then the expression a+-b can be accurately interpreted as a + (-b) , but if a new operation +- is declared in the program, it immediately arises ambiguity, because the same expression can already be parsed as a (+-) b . The developer and implementer of the language must deal with such problems in some way. The options, again, can be different: require that all new operations be single-character, postulate that in case of any discrepancies, the “longest” version of the operation is chosen (that is, until the next set of characters read by the translator matches any operation, it continues to be read), try to detect collisions during translation and generate errors in controversial cases ... One way or another, languages ​​that allow the declaration of new operations solve these problems.

It should not be forgotten that for new operations there is also the issue of determining associativity and priority. There is no longer a ready-made solution in the form of a standard language operation, and usually you just have to set these parameters with the rules of the language. For example, make all new operations left-associative and give them the same, fixed, priority, or introduce into the language the means of specifying both.

Overloading and polymorphic variables

When overloaded operators, functions, and procedures are used in strongly typed languages, where each variable has a pre-declared type, it is up to the compiler to decide which version of the overloaded operator to use in each particular case, no matter how complex. This means that for compiled languages, the use of operator overloading does not lead to performance degradation - in any case, there is a well-defined operation or function call in the object code of the program. The situation is different when it is possible to use polymorphic variables in the language, that is, variables that can contain values ​​of different types at different times.

Since the type of the value to which the overloaded operation will be applied is not known at the time of translation of the code, the compiler is unable to choose desired option in advance. In this case, it is forced to embed a fragment in the object code that, immediately before performing this operation, will determine the types of the values ​​in the arguments and dynamically select a variant corresponding to this set of types. Moreover, such a definition must be made at each execution of the operation, because even the same code, being called a second time, may well be executed differently.

Thus, the use of operator overloading in combination with polymorphic variables makes it inevitable to dynamically determine which code to call.

Criticism

The use of overload is not considered a boon by all experts. If function and procedure overloading is generally not objectionable (partly because it doesn't lead to some typical "operator" problems, partly because it's less tempting to misuse it), then operator overloading is, in principle, , and in specific language implementations, is subjected to quite severe criticism from many programming theorists and practitioners.

Critics point out that the problems of identification, precedence, and associativity outlined above often make dealing with overloaded operators either unnecessarily difficult or unnatural:

  • Identification. If the language has strict identification rules, then the programmer is forced to remember for which combinations of types there are overloaded operations and manually cast operands to them. If the language allows "approximate" identification, one can never be sure that in some rather complicated situation, exactly the variant of the operation that the programmer had in mind will be performed.
  • Priority and associativity. If they are rigidly defined, this may be inconvenient and not relevant to the subject area (for example, for operations with sets, priorities differ from arithmetic ones). If they can be set by the programmer, this becomes an additional source of errors (if only because different variants of one operation turn out to have different priorities, or even associativity).

How much the convenience of using your own operations can outweigh the inconvenience of deteriorating the controllability of the program is a question that does not have a clear answer.

From the point of view of the language implementation, the same problems lead to the complexity of translators and the decrease in their efficiency and reliability. And the use of overloading in conjunction with polymorphic variables is also obviously slower than calling a hardcoded operation during compilation, and provides fewer opportunities for optimizing the object code. Specific features of the implementation of overloading in various languages ​​are subjected to separate criticism. So, in C++, the object of criticism can be the lack of an agreement on the internal representation of the names of overloaded functions, which gives rise to incompatibility at the level of libraries compiled by different C++ compilers.

Some critics speak out against overloading operations, based on general principles development theory software and real industrial practice.

  • Proponents of the "puritan" approach to language building, such as Wirth or Hoare, oppose operator overloading simply because it can be easily dispensed with. In their opinion, such tools only complicate the language and the translator, without providing corresponding to this complication additional features. In their opinion, the very idea of ​​creating a task-oriented extension of the language only looks attractive. In reality, the use of language extension tools makes the program understandable only to its author - the one who developed this extension. The program becomes much more difficult for other programmers to understand and analyze, making maintenance, modification, and team development more difficult.
  • It is noted that the very possibility of using overload often plays a provocative role: programmers start using it wherever possible, as a result, a tool designed to simplify and streamline the program becomes the cause of its complication and confusion.
  • Overloaded operators may not do exactly what is expected of them, based on their kind. For example, a + b usually (but not always) means the same thing as b + a , but "one" + "two" is different from "two" + "one" in languages ​​where the + operator is overloaded for string concatenation.
  • Operator overloading makes program fragments more context-sensitive. Without knowing the types of the operands involved in an expression, it is impossible to understand what the expression does if it uses overloaded operators. For example, in a C++ program, the statement<< может означать и побитовый сдвиг, и вывод в поток. Выражение a << 1 возвращает результат побитового сдвига значения a на один бит влево, если a - целая переменная, но если a является выходным потоком , то же выражение выведет в этот поток строку «1» .

Classification

The following is a classification of some programming languages ​​according to whether they allow operator overloading, and whether operators are limited to a predefined set:

Operations No overload There is an overload
Limited set of operations
  • Objective-C
  • Python
It is possible to define new operations
  • PostgreSQL
  • see also

    Wikimedia Foundation. 2010 .

    See what "Function Overloading" is in other dictionaries:

      - (operators, functions, procedures) in programming one of the ways to implement polymorphism, which consists in the possibility of simultaneous existence in one scope of several different variants of an operation (operator, function or ... ... Wikipedia

Function overloading is the definition of several functions (two or more) with the same name but different parameters. The parameter sets of overloaded functions may differ in order, number, and type. Thus, function overloading is needed in order to avoid duplicating the names of functions that perform similar actions, but with different program logic. For example, consider the areaRectangle() function, which calculates the area of ​​a rectangle.

Float areaRectangle(float, float) //function that calculates the area of ​​a rectangle with two parameters a(cm) and b(cm) ( return a * b; // multiply the lengths of the sides of the rectangle and return the resulting product )

So, this is a function with two float type parameters, and the arguments passed to the function must be in centimeters, the return value of the float type is also in centimeters.

Suppose that our initial data (rectangle sides) are given in meters and centimeters, for example: a = 2m 35 cm; b = 1m 86 cm. In this case, it would be convenient to use a function with four parameters. That is, each length of the sides of the rectangle is passed to the function in two parameters: meters and centimeters.

Float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // function that calculates the area of ​​a rectangle with 4 parameters a(m) a(cm); b(m) b(cm) ( return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); )

In the body of the function, the values ​​that were passed in meters (a_m and b_m) are converted to centimeters and added to the values ​​a_sm b_sm , after which we multiply the sums and get the area of ​​the rectangle in cm. Of course, it was possible to convert the original data into centimeters and use the first function, but now is not about that.

Now, the most important thing is that we have two functions, with different signatures, but the same names (overloaded functions). A signature is a combination of a function name with its parameters. How to call these functions? And calling overloaded functions is no different from calling regular functions, for example:

AreaRectangle(32, 43); // a function will be called that calculates the area of ​​a rectangle with two parameters a(cm) and b(cm) areaRectangle(4, 43, 2, 12); // a function will be called that calculates the area of ​​a rectangle with 4 parameters a(m) a(cm); b(m) b(cm)

As you can see, the compiler will independently select the desired function, analyzing only the signatures of overloaded functions. Bypassing function overloading, one could simply declare a function with a different name, and it would do its job well. But imagine what will happen if you need more than two such functions, for example 10. And for each you need to come up with a meaningful name, and the hardest thing to remember is them. That's exactly why it's easier and better to overload functions, unless of course there is a need for this. The source code of the program is shown below.

#include "stdafx.h" #include << "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

// code Code::Blocks

// Dev-C++ code

#include using namespace std; // prototypes of overloaded functions float areaRectangle(float a, float b); float areaRectangle(float a_m, float a_sm, float b_m, float b_sm); int main() ( cout<< "S1 = " << areaRectangle(32,43) << endl; // вызов перегруженной функции 1 cout << "S2 = " << areaRectangle(4, 43, 2, 12) << endl; // вызов перегруженной функции 2 return 0; } // перегруженная функция 1 float areaRectangle(float a, float b) //функция, вычисляющая площадь прямоугольника с двумя параметрами a(см) и b(см) { return a * b; // умножаем длинны сторон прямоугольника и возвращаем полученное произведение } // перегруженная функция 2 float areaRectangle(float a_m, float a_sm, float b_m, float b_sm) // функция, вычисляющая площадь прямоугольника с 4-мя параметрами a(м) a(см); b(м) b(cм) { return (a_m * 100 + a_sm) * (b_m * 100 + b_sm); }

The result of the program is shown in Figure 1.



How to achieve function overloading in C? (10)

Is there a way to achieve function overloading in C? I'm looking at simple functions that can be overloaded like

foo (int a) foo (char b) foo (float c , int d)

I think there is no direct way; I'm looking for workarounds, if any exist.

I hope the below code will help you understand function overloading

#include #include int fun(int a, ...); int main(int argc, char *argv)( fun(1,10); fun(2,"cquestionbank"); return 0; ) int fun(int a, ...)( va_list vl; va_start(vl,a ); if(a==1) printf("%d",va_arg(vl,int)); else printf("\n%s",va_arg(vl,char *)); )

I mean, you mean - no, you can't.

You can declare the va_arg function as

void my_func(char* format, ...);

But you will need to pass some information about the number of variables and their types in the first argument - like printf() .

Yes, like.

Here you give an example:

void printA(int a)( printf("Hello world from printA: %d\n",a); ) void printB(const char *buff)( printf("Hello world from printB: %s\n",buff) ; ) #define Max_ITEMS() 6, 5, 4, 3, 2, 1, 0 #define __VA_ARG_N(_1, _2, _3, _4, _5, _6, N, ...) N #define _Num_ARGS_(... ) __VA_ARG_N(__VA_ARGS__) #define NUM_ARGS(...) (_Num_ARGS_(_0, ## __VA_ARGS__, Max_ITEMS()) - 1) #define CHECK_ARGS_MAX_LIMIT(t) if(NUM_ARGS(args)>t) #define CHECK_ARGS_MIN_LIMIT(t) if(NUM_ARGS(args) #define print(x , args ...) \ CHECK_ARGS_MIN_LIMIT(1) printf("error");fflush(stdout); \ CHECK_ARGS_MAX_LIMIT(4) printf("error");fflush(stdout) ; \ (( \ if (__builtin_types_compatible_p (typeof (x), int)) \ printA(x, ##args); \ else \ printB (x,##args); \ )) int main(int argc, char* * argv) ( int a=0; print(a); print("hello"); return (EXIT_SUCCESS); )

It will output 0 and hello from printA and printB.

If your compiler is gcc and you don't mind doing manual updates every time you add a new overload, you can make a macro mass and get the result you want from the callers point of view, not so nice to write... but it's possible

look at __builtin_types_compatible_p then use it to define a macro that does something like

#define foo(a) \ ((__builtin_types_compatible_p(int, a)?foo(a):(__builtin_types_compatible_p(float, a)?foo(a):)

but yeah, nasty, just not

EDIT: C1X will get support for type expressions, which they look like this:

#define cbrt(X) _Generic((X), long double: cbrtl, \ default: cbrt, \ float: cbrtf)(X)

As already stated, overloading in the sense that you mean is not supported by C. The usual idiom to solve the problem is for the function to take a tagged union . This is implemented using the struct parameter, where the struct itself consists of some type of type indicator, such as an enum , and a union of different value types. Example:

#include typedef enum ( T_INT, T_FLOAT, T_CHAR, ) my_type; typedef struct ( my_type type; union ( int a; float b; char c; ) my_union; ) my_struct; void set_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: whatever->my_union.a = 1; break; case T_FLOAT: whatever->my_union.b = 2.0; break; case T_CHAR: whatever-> my_union.c = "3"; ) ) void printf_overload (my_struct *whatever) ( switch (whatever->type) ( case T_INT: printf("%d\n", whatever->my_union.a); break; case T_FLOAT : printf("%f\n", whatever->my_union.b); break; case T_CHAR: printf("%c\n", whatever->my_union.c); break; ) ) int main (int argc, char* argv) ( my_struct s; s.type=T_INT; set_overload(&s); printf_overload(&s); s.type=T_FLOAT; set_overload(&s); printf_overload(&s); s.type=T_CHAR; set_overload(&s) ; printf_overload(&s); )

Can't you just use C++ and not use all other C++ features besides this one?

If there hasn't been strictly strict C so far, I would recommend variadic functions instead.

The following approach is similar to a2800276, but with some C99 macros:

// we need `size_t` #include // argument types to accept enum sum_arg_types ( SUM_LONG, SUM_ULONG, SUM_DOUBLE ); // a structure to hold an argument struct sum_arg ( enum sum_arg_types type; union ( long as_long; unsigned long as_ulong; double as_double; ) value; ); // determine an array"s size #define count(ARRAY) ((sizeof (ARRAY))/(sizeof *(ARRAY))) // this is how our function will be called #define sum(...) _sum( count(sum_args(__VA_ARGS__)), sum_args(__VA_ARGS__)) // create an array of `struct sum_arg` #define sum_args(...) ((struct sum_arg )( __VA_ARGS__ )) // create initializers for the arguments #define sum_long (VALUE) ( SUM_LONG, ( .as_long = (VALUE) ) ) #define sum_ulong(VALUE) ( SUM_ULONG, ( .as_ulong = (VALUE) ) ) #define sum_double(VALUE) ( SUM_DOUBLE, ( .as_double = (VALUE) ) ) // our polymorphic function long double _sum(size_t count, struct sum_arg * args) ( long double value = 0; for(size_t i = 0; i< count; ++i) { switch(args[i].type) { case SUM_LONG: value += args[i].value.as_long; break; case SUM_ULONG: value += args[i].value.as_ulong; break; case SUM_DOUBLE: value += args[i].value.as_double; break; } } return value; } // let"s see if it works #include int main() ( unsigned long foo = -1; long double value = sum(sum_long(42), sum_ulong(foo), sum_double(1e10)); printf("%Le\n", value); return 0; )

For the time being, _Generic since the question's _Generic, standard C (no extensions) is effectively received support for overloading functions (rather than operators) thanks to the addition of the _Generic word _Generic in C11. (supported in GCC since version 4.9)

(Overloading isn't truly "built in" in the way shown in the question, but it's easy to destroy something that works like this.)

Generic is a compile-time operator in the same family as sizeof and _Alignof . It is described in the standard section 6.5.1.1. It takes two main parameters: an expression (which won't be evaluated at runtime) and a list of type/expression associations, which is a bit like a switch block. _Generic gets the generic type of the expression and then "switches" to it to select the final result expression in the list for its type:

Generic(1, float: 2.0, char *: "2", int: 2, default: get_two_object());

The above expression evaluates to 2 - the type of the control expression is int , so it selects the expression associated with int as the value. None of this is left at runtime. (The default clause is mandatory: if you don't specify it and the type doesn't match, it will cause a compilation error.)

A technique that is useful for function overloading is that it can be inserted by the C preprocessor and select a result expression based on the type of the arguments passed to the controlling macro. So (example from the C standard):

#define cbrt(X) _Generic((X), \ long double: cbrtl, \ default: cbrt, \ float: cbrtf \)(X)

This macro implements the overloaded cbrt operation by passing on the type of the argument to the macro, selecting the appropriate implementation function, and then passing the original macro to that function.

So, to implement your original example, we could do this:

Foo_int (int a) foo_char (char b) foo_float_int (float c , int d) #define foo(_1, ...) _Generic((_1), \ int: foo_int, \ char: foo_char, \ float: _Generic(( FIRST(__VA_ARGS__,)), \int: foo_float_int))(_1, __VA_ARGS__) #define FIRST(A, ...) A

In this case, we could use the default: binding for the third case, but that doesn't demonstrate how to extend the principle to multiple arguments. The end result is that you can use foo(...) in your code without worrying (much) about the type of your arguments.

For more complex situations, such as functions that overload more arguments or change numbers, you can use utility macros to automatically generate static dispatch structures:

void print_ii(int a, int b) ( printf("int, int\n"); ) void print_di(double a, int b) ( printf("double, int\n"); ) void print_iii(int a, int b, int c) ( printf("int, int, int\n"); ) void print_default(void) ( printf("unknown arguments\n"); ) #define print(...) OVERLOAD(print, (__VA_ARGS__), \ (print_ii, (int, int)), \ (print_di, (double, int)), \ (print_iii, (int, int, int)) \) #define OVERLOAD_ARG_TYPES (int, double) #define OVERLOAD_FUNCTIONS (print) #include "activate-overloads.h" int main(void) ( print(44, 47); // prints "int, int" print(4.4, 47); // prints "double, int" print (1, 2, 3); // prints "int, int, int" print(""); // prints "unknown arguments" )

(implementation here). With some effort, you can reduce the boilerplate to look pretty similar to a language with built-in overload support.

Aside, it was already possible to overload quantity arguments (rather than type) in C99.

Note that the way C is evaluated can move you. This will select foo_int if you try to pass a literal character to it, for example, and you need some foo_int if you want your overloads to support string literals. However, overall pretty cool.

Leushenko's answer is really cool: only the foo example doesn't compile with GCC, which fails on foo(7) , bumping into the FIRST macro and the actual function call ((_1, __VA_ARGS__) , remaining with an extra comma. Also, we run into problems, if we want to provide additional overloads like foo(double) .

So I decided to answer this question in more detail, including allowing the empty overload (foo(void) - which caused some trouble...).

The idea now is: define more than one generic in different macros and let the correct one be chosen according to the number of arguments!

The number of arguments is pretty simple, based on this answer:

#define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) #define CONCAT(X, Y) CONCAT_(X, Y) # define CONCAT_(X, Y) X ## Y

That's good, we're deciding either SELECT_1 or SELECT_2 (or more arguments if you want/need them), so we just need the appropriate definitions:

#define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char: foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double : _Generic((_2), \ int: foo_double_int \) \)

First, an empty macro call (foo()) still creates a token, but it's empty. So the count macro actually returns 1 instead of 0, even if the macro is called empty. We can "easily" fix this problem if we __VA_ARGS__ with a comma after __VA_ARGS__ conditionally, depending on whether the list is empty or not:

#define NARG(...) ARG4_(__VA_ARGS__ COMMA(__VA_ARGS__) 4, 3, 2, 1, 0)

This looked easy, but the COMMA macro is quite heavy; fortunately, this topic is already covered in Jens Gustedt's blog (thanks, Jens). The main trick is that function macros don't expand unless followed by parentheses, see Jens blog for further explanation... We just need to modify the macros a bit for our needs (I'll use shorter names and fewer arguments for brevity).

#define ARGN(...) ARGN_(__VA_ARGS__) #define ARGN_(_0, _1, _2, _3, N, ...) N #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 1, 0 ) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_COMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3 # define COMMA_0000 , #define COMMA_0001 #define COMMA_0010 , // ... (all others with comma) #define COMMA_1111 ,

And now we're fine...

Full code in one block:

/* * demo.c * * Created on: 2017-09-14 * Author: sboehler */ #include void foo_void(void) ( puts("void"); ) void foo_int(int c) ( printf("int: %d\n", c); ) void foo_char(char c) ( printf("char: %c \n", c); ) void foo_double(double c) ( printf("double: %.2f\n", c); ) void foo_double_int(double c, int d) ( printf("double: %.2f, int: %d\n", c, d); ) #define foo(...) SELECT(__VA_ARGS__)(__VA_ARGS__) #define SELECT(...) CONCAT(SELECT_, NARG(__VA_ARGS__))(__VA_ARGS__) # define CONCAT(X, Y) CONCAT_(X, Y) #define CONCAT_(X, Y) X ## Y #define SELECT_0() foo_void #define SELECT_1(_1) _Generic ((_1), \ int: foo_int, \ char : foo_char, \ double: foo_double \) #define SELECT_2(_1, _2) _Generic((_1), \ double: _Generic((_2), \ int: foo_double_int \) \) #define ARGN(...) ARGN_( __VA_ARGS__) #define ARGN_(_0, _1, _2, N, ...) N #define NARG(...) ARGN(__VA_ARGS__ COMMA(__VA_ARGS__) 3, 2, 1, 0) #define HAS_COMMA(...) ARGN(__VA_ARGS__, 1, 1, 0) #define SET_COMMA(...) , #define COMMA(...) SELECT_COMMA \ (\ HAS_COMMA(__VA_ARGS__), \ HAS_COMMA(__VA_ARGS__ ()), \ HAS_C OMMA(SET_COMMA __VA_ARGS__), \ HAS_COMMA(SET_COMMA __VA_ARGS__ ()) \) #define SELECT_COMMA(_0, _1, _2, _3) SELECT_COMMA_(_0, _1, _2, _3) #define SELECT_COMMA_(_0, _1, _2, _3) COMMA_ ## _0 ## _1 ## _2 ## _3 COMMA_1001 , #define COMMA_1010 , #define COMMA_1011 , #define COMMA_1100 , #define COMMA_1101 , #define COMMA_1110 , #define COMMA_1111 , int main(int argc, char** argv) ( foo(); foo(7); foo(10.12); foo(12.10, 7); foo((char)"s"); return 0; )

Annotation: The lecture discusses the concepts, declaration and use of inline and overloaded functions in C++ programs, mechanisms for performing substitution and function overloading, recommendations for improving the efficiency of programs through overloading or function substitution.

The purpose of the lecture: learn inline (embedded) functions and function overloads, learn how to develop programs using function overloading in C ++.

Inline functions

Calling a function, passing values ​​to it, returning a value - these operations take quite a lot of CPU time. Usually, when defining a function, the compiler reserves only one block of cells in memory to store its statements. After the function is called, program control is transferred to these operators, and upon return from the function, program execution resumes from the line following the function call.

With repeated calls, each time the program will process the same set of commands, without creating copies for each call separately.

Each jump to the memory area containing the function statements slows down the execution of the program. If the function is small, you can save time on multiple calls by instructing the compiler to inline the function code directly into the program at the call site. Such functions are called substituted. In this case, speaking of efficiency, first of all, the speed of program execution is implied.

Inline or Inline Functions are functions whose code is inserted by the compiler directly at the call site, instead of transferring control to a single instance of the function.

If the function is inline, the compiler does not create the given function in memory, but copies its strings directly into the program code at the place of the call. This is equivalent to writing appropriate blocks in the program instead of function calls. So the specifier inline defines for the function the so-called internal binding, which lies in the fact that the compiler substitutes the commands of its code instead of calling the function. Inline functions are used if the function body consists of several statements.

This approach allows you to increase the speed of program execution, since commands are excluded from the program. microprocessor The required to pass arguments and call the function.

For example:

/*function returns distance from point with coordinates(x1,y1) to point with coordinates (x2,y2)*/ inline float Line(float x1,float y1,float x2, float y2) ( return sqrt(pow(x1-x2 ,2)+pow(y1-y2,2)); )

However, it should be noted that the use of inline functions does not always lead to a positive effect. If such a function is called several times in the program code, then in compilation time as many copies of this function as there are calls to it will be inserted into the program. There will be a significant increase in the size of the program code, as a result of which the expected increase in the efficiency of program execution in time may not occur.

Example 1.

#include "stdafx.h" #include using namespace std; inline int Cube(int x); int _tmain(int argc, _TCHAR* argv)( int x=2; float y=3; double z=4; cout<

We list the reasons why a function with the inline specifier will be treated as a regular non-inline function:

  • the inline function is recursive;
  • functions whose call is placed before its definition;
  • functions that are called more than once in an expression;
  • functions containing loops, switches, and jump operators;
  • functions that are too large to allow substitution.

Restrictions on performing substitution are mostly implementation dependent. If for a function with a specifier inline the compiler cannot perform the substitution because of the context in which the call to it is placed, then the function is considered static and is issued warning message.

Another feature of inline functions is the impossibility of changing them without recompiling all parts of the program in which these functions are called.

Function overload

When defining functions in programs, it is necessary to specify the type of the value returned by the function, as well as the number of parameters and the type of each of them. If there was a function in C++ called add_values ​​that operated on two integer values, and the program needed to use a similar function to pass three integer values, then a function with a different name would have to be created. For example, add_two_values ​​and add_three_values ​​. Similarly, if you want to use a similar function to work with float values, then you need another function with another name. To avoid function duplication, C++ allows you to define multiple functions with the same name. During compilation, C++ takes into account the number of arguments used by each function and then calls exactly the required function. Giving the compiler a choice among multiple functions is called overloading.

Function overloading is the creation of several functions with the same name, but with different parameters. Different parameters mean what should be different number of arguments functions and/or their type. That is, function overloading allows you to define multiple functions with the same name and return type.

Function overloading is also called function polymorphism. "Poly" means a lot, "morph" - a form, that is, a polymorphic function is a function that is distinguished by a variety of forms.

Function polymorphism is understood as the existence in the program of several overloaded versions of a function that have different values. By changing the number or type of parameters, you can give two or more functions the same name. In this case, there will be no confusion, since the desired function is determined by the coincidence of the parameters used. This allows you to create a function that can work with integers, floats, or other types of values ​​without having to create separate names for each function.

Thus, thanks to the use overloaded functions, you should not worry about calling the right function in the program that matches the type of variables being passed. When you call an overloaded function, the compiler will automatically determine which version of the function should be used.

For example, the following program overloads a function named add_values ​​. The first function definition adds two values ​​of type int . The second function definition adds three values ​​of type int . During compilation, C++ correctly determines the function to be used:

#include "stdafx.h" #include using namespace std; int add_values(int a,int b); int add_values ​​(int a, int b, int c); int _tmain(int argc, _TCHAR* argv)( cout<< "200+801=" << add_values(200,801) << "\n"; cout << "100+201+700=" << add_values(100,201,700) << "\n"; system("pause"); return 0; } int add_values(int a,int b) { return(a + b); } int add_values (int a, int b, int c) { return(a + b + c); }

Thus, the program defines two functions named add_values ​​. The first function adds two values, while the second adds three values ​​of the same int type. The C++ compiler determines which function to use based on the options provided by the program.

Using function overload

One of the most common use cases for overloading is to use a function to produce a specific result given various parameters. For example, suppose your program has a function named day_of_week that returns the current day of the week (0 for Sunday, 1 for Monday, ... , 6 for Saturday). A program could overload this function so that it correctly returns the day of the week if it is given a Julian day as a parameter, or if it is given a day, month, and year.

int day_of_week(int julian_day) ( // operators ) int day_of_week(int month, int day, int year) ( // operators )

Using overloaded functions a number of errors are often made. For example, if functions differ only in return type, but not in argument types, such functions cannot have the same name. The following overload option is also invalid:

int function_name(int argument_name); int function_name(int argument_name); /* invalid name overload: arguments have the same number and same type */

Benefits of function overloading:

  • function overloading improves readability programs;
  • C++ function overloading allows programs to define multiple functions with the same name;
  • overloaded functions return values ​​of the same type, but may differ in the number and type of parameters;
  • function overloading simplifies the task of programmers by requiring them to remember only one function name, but then they must know which combination of parameters corresponds to which function.

Example 2.

/*Overloaded functions have the same name but different parameter lists and return values*/ #include "stdafx.h" #include using namespace std; int average(int first_number, int second_number, int third_number); int average(int first_number, int second_number); int _tmain(int argc, _TCHAR* argv)(// main function int number_A = 5, number_B = 3, number_C = 10; cout<< "Целочисленное среднее чисел " << number_A << " и "; cout << number_B << " равно "; cout << average(number_A, number_B) << ".\n\n"; cout << "Целочисленное среднее чисел " << number_A << ", "; cout << number_B << " и " << number_C << " равно "; cout << average(number_A, number_B, number_C) << ".\n"; system("PAUSE"); return 0; }// конец главной функции /*функция для вычисления целочисленного среднего значения 3-х целых чисел*/ int average(int first_number, int second_number, int third_number) { return((first_number + second_number + third_number)/3); } // конец функции /*функция для вычисления целочисленного среднего значения 2-х целых чисел*/ int average(int first_number, int second_number) { return((first_number + second_number)/2); } // конец функции



Loading...
Top