B. Stroustrup - The C++ Programming Language (794319), страница 70
Текст из файла (страница 70)
For example, thisis two declarations of the same function:void f(int);void f(const int);// type is void(int)// type is void(int)308FunctionsChapter 12That function, f(), could be defined as:void f(int x) { /*we can modify x here */ }Alternatively, we could define f() as:void f(const int x) { /*we cannot modify x here */ }In either case, the argument that f() can or cannot modify is a copy of what a caller provided, sothere is no danger of an obscure modification of the calling context.Function argument names are not part of the function type and need not be identical in differentdeclarations. For example:int& max(int& a, int& b, int& c); // return a reference to the larger of a, b, and cint& max(int& x1, int& x2, int& x3){return (x1>x2)? ((x1>x3)?x1:x3) : ((x2>x3)?x2:x3);}Naming arguments in declarations that are not definitions is optional and commonly used to simplify documentation.
Conversely, we can indicate that an argument is unused in a function definition by not naming it. For example:void search(table∗ t, const char∗ key, const char∗){// no use of the third argument}Typically, unnamed arguments arise from the simplification of code or from planning ahead forextensions. In both cases, leaving the argument in place, although unused, ensures that callers arenot affected by the change.In addition to functions, there are a few other things that we can call; these follow most rulesdefined for functions, such as the rules for argument passing (§12.2):• Constructors (§2.3.2, §16.2.5) are technicallly not functions; in particular, they don’t returna value, can initialize bases and members (§17.4), and can’t have their address taken.• Destructors (§3.2.1.2, §17.2) can’t be overloaded and can’t have their address taken.• Function objects (§3.4.3, §19.2.2) are not functions (they are objects) and can’t be overloaded, but their operator()s are functions.• Lambda expressions (§3.4.3, §11.4) are basically a shorthand for defining function objects.12.1.4 Returning ValuesEvery function declaration contains a specification of the function’s return type (except for constructors and type conversion functions).
Traditionally, in C and C++, the return type comes first ina function declaration (before the name of the function). However, a function declaration can alsobe written using a syntax that places the return type after the argument list. For example, the following two declarations are equivalent:string to_string(int a);auto to_string(int a) −> string;// prefix return type// suffix return typeSection 12.1.4Returning Values309That is, a prefix auto indicates that the return type is placed after the argument list. The suffixreturn type is preceded by −>.The essential use for a suffix return type comes in function template declarations in which thereturn type depends on the arguments.
For example:template<class T, class U>auto product(const vector<T>& x, const vector<U>& y) −> decltype(x∗y);However, the suffix return syntax can be used for any function. There is an obvious similaritybetween the suffix return syntax for a function and the lambda expression syntax (§3.4.3, §11.4); itis a pity those two constructs are not identical.A function that does not return a value has a ‘‘return type’’ of void.A value must be returned from a function that is not declared void (however, main() is special;see §2.2.1). Conversely, a value cannot be returned from a void function.
For example:int f1() { }void f2() { }// error: no value returned// OKint f3() { return 1; }void f4() { return 1; }// OK// error : return value in void functionint f5() { return; }void f6() { return; }// error : return value missing// OKA return value is specified by a return-statement. For example:int fac(int n){return (n>1) ? n∗fac(n−1) : 1;}A function that calls itself is said to be recursive.There can be more than one return-statement in a function:int fac2(int n){if (n > 1)return n∗fac2(n−1);return 1;}Like the semantics of argument passing, the semantics of function value return are identical to thesemantics of copy initialization (§16.2.6).
A return-statement initializes a variable of the returnedtype. The type of a return expression is checked against the type of the returned type, and all standard and user-defined type conversions are performed. For example:double f() { return 1; }// 1 is implicitly converted to double{1}Each time a function is called, a new copy of its arguments and local (automatic) variables is created. The store is reused after the function returns, so a pointer to a local non-static variable shouldnever be returned.
The contents of the location pointed to will change unpredictably:310FunctionsChapter 12int∗ fp(){int local = 1;// ...return &local; // bad}An equivalent error can occur when using references:int& fr(){int local = 1;// ...return local;}// badFortunately, a compiler can easily warn about returning references to local variables (and most do).There are no void values. However, a call of a void function may be used as the return value of avoid function. For example:void g(int∗ p);void h(int∗ p){// ...return g(p);}// OK: equivalent to ‘‘g(p); return;’’This form of return is useful to avoid special cases when writing template functions where thereturn type is a template parameter.A return-statement is one of five ways of exiting a function:• Executing a return-statement.• ‘‘Falling off the end’’ of a function; that is, simply reaching the end of the function body.This is allowed only in functions that are not declared to return a value (i.e., void functions)and in main(), where falling off the end indicates successful completion (§12.1.4).• Throwing an exception that isn’t caught locally (§13.5).• Terminating because an exception was thrown and not caught locally in a noexcept function(§13.5.1.1).• Directly or indirectly invoking a system function that doesn’t return (e.g., exit(); §15.4).A function that does not return normally (i.e., through a return or ‘‘falling off the end’’) can bemarked [[noreturn]] (§12.1.7).12.1.5 inline FunctionsA function can be defined to be inline.
For example:inline int fac(int n){return (n<2) ? 1 : n∗fac(n−1);}Section 12.1.5inlineFunctions311The inline specifier is a hint to the compiler that it should attempt to generate code for a call of fac()inline rather than laying down the code for the function once and then calling through the usualfunction call mechanism.
A clever compiler can generate the constant 720 for a call fac(6). Thepossibility of mutually recursive inline functions, inline functions that recurse or not depending oninput, etc., makes it impossible to guarantee that every call of an inline function is actually inlined.The degree of cleverness of a compiler cannot be legislated, so one compiler might generate 720,another 6∗fac(5), and yet another an un-inlined call fac(6).
If you want a guarantee that a value iscomputed at compile time, declare it constexpr and make sure that all functions used in its evaluation are constexpr (§12.1.6).To make inlining possible in the absence of unusually clever compilation and linking facilities,the definition – and not just the declaration – of an inline function must be in scope (§15.2). Aninline specifier does not affect the semantics of a function.
In particular, an inline function still hasa unique address, and so do static variables (§12.1.8) of an inline function.If an inline function is defined in more than one translation unit (e.g., typically because it wasdefined in a header; §15.2.2), its definition in the different translation units must be identical(§15.2.3).12.1.6 constexpr FunctionsIn general, a function cannot be evaluated at compile time and therefore cannot be called in a constant expression (§2.2.3, §10.4).
By specifying a function constexpr, we indicate that we want it tobe usable in constant expressions if given constant expressions as arguments. For example:constexpr int fac(int n){return (n>1) ? n∗fac(n−1) : 1;}constexpr int f9 = fac(9);// must be evaluated at compile timeWhen constexpr is used in a function definition, it means ‘‘should be usable in a constant expression when given constant expressions as arguments.’’ When used in an object definition, it means‘‘evaluate the initializer at compile time.’’ For example:void f(int n){int f5 = fac(5);int fn = fac(n);// may be evaluated at compile time// evaluated at run time (n is a variable)constexpr int f6 = fac(6);constexpr int fnn = fac(n);// must be evaluated at compile time// error : can’t guarantee compile-time evaluation (n is a variable)char a[fac(4)];char a2[fac(n)];// OK: array bounds must be constants and fac() is constexpr// error : array bounds must be constants and n is a variable// ...}To be evaluated at compile time, a function must be suitably simple: aconstexprfunction must312FunctionsChapter 12consist of a single return-statement; no loops and no local variables are allowed.
Also, a constexprfunction may not have side effects. That is, a constexpr function is a pure function. For example:int glob;constexpr void bad1(int a)// error : constexpr function cannot be void{glob = a;// error: side effect in constexpr function}constexpr int bad2(int a){if (a>=0) return a; else return −a;}constexpr int bad3(int a){sum = 0;for (int i=0; i<a; +=i) sum +=fac(i);return sum;}// error : if-statement in constexpr function// error : local variable in constexpr function// error : loop in constexpr functionThe rules for a constexpr constructor are suitably different (§10.4.3); there, only simple initialization of members is allowed.A constexpr function allows recursion and conditional expressions.
This implies that you canexpress just about anything as a constexpr function if you really want to. However, you’ll find thedebugging gets unnecessarily difficult and compile times longer than you would like unless yourestrict the use of constexpr functions to the relatively simple tasks for which they are intended.By using literal types (§10.4.3), constexpr functions can be defined to use user-defined types.Like inline functions, constexpr functions obey the ODR (‘‘one-definition rule’’), so that definitions in the different translation units must be identical (§15.2.3).