B. Stroustrup - The C++ Programming Language (794319), страница 71
Текст из файла (страница 71)
You can think of constexpr functions as a restricted form of inline functions (§12.1.5).12.1.6.1 constexpr and ReferencesA constexpr function cannot have side effects, so writing to nonlocal objects is not possible. However, a constexpr function can refer to nonlocal objects as long as it does not write to them.constexpr int ftbl[] { 1, 2, 3, 5, 8, 13 };constexpr int fib(int n){return (n<sizeof(ftbl)/sizeof(∗ftbl)) ? ftbl[n] : fib(n);}A constexpr function can take reference arguments.
Of course, it cannot write through such references, but const reference parameters are as useful as ever. For example, in the standard library(§40.4) we find:Section 12.1.6.1constexprand References313template<> class complex<float> {public:// ...explicit constexpr complex(const complex<double>&);// ...};This allows us to write:constexpr complex<float> z {2.0};The temporary variable that is logically constructed to hold the const reference argument simplybecomes a value internal to the compiler.It is possible for a constexpr function to return a reference or a pointer.
For example:constexpr const int∗ addr(const int& r) { return &r; }// OKHowever, doing so brings us away from the fundamental role of constexpr functions as parts of constant expression evaluation. In particular, it can be quite tricky to determine whether the result ofsuch a function is a constant expression. Consider:static const int x = 5;constexpr const int∗ p1 = addr(x);constexpr int xx = ∗p1;// OK// OKstatic int y;constexpr const int∗ p2 = addr(y);constexpr int yy = ∗y;// OK// error : attempt to read a variableconstexpr const int∗ tp = addr(5);// error : address of temporar y12.1.6.2 Conditional EvaluationA branch of a conditional expression that is not taken in a constexpr function is not evaluated.
Thisimplies that a branch not taken can require run-time evaluation. For example:constexpr int check(int i){return (low<=i && i<high) ? i : throw out_of_range();}constexpr int low = 0;constexpr int high = 99;// ...constexpr int val = check(f(x,y,z));You might imagine low and high to be configuration parameters that are known at compile time, butnot at design time, and that f(x,y,z) computes some implementation-dependent value.314FunctionsChapter 1212.1.7 [[noreturn]] FunctionsA construct [[...]] is called an attribute and can be placed just about anywhere in the C++ syntax.In general, an attribute specifies some implementation-dependent property about the syntacticentity that precedes it.
In addition, an attribute can be placed in front of a declaration. There areonly two standard attributes (§iso.7.6), and [[noreturn]] is one of them. The other is [[carries_dependency]] (§41.3).Placing [[noreturn]] at the start of a function declaration indicates that the function is notexpected to return. For example:[[noreturn]] void exit(int);// exit will never returnKnowing that a function does not return is useful for both comprehension and code generation.What happens if the function returns despite a [[noreturn]] attribute is undefined.12.1.8 Local VariablesA name defined in a function is commonly referred to as a local name.
A local variable or constantis initialized when a thread of execution reaches its definition. Unless declared static, each invocation of the function has its own copy of the variable. If a local variable is declared static, a single,statically allocated object (§6.4.2) will be used to represent that variable in all calls of the function.It will be initialized only the first time a thread of execution reaches its definition.
For example:void f(int a){while (a−−) {static int n = 0;int x = 0;// initialized once// initialized ’a’ times in each call of f()cout << "n == " << n++ << ", x == " << x++ << '\n';}}int main(){f(3);}This prints:n == 0, x == 0n == 1, x == 0n == 2, x == 0A static local variable allows the function to preserve information between calls without introducing a global variable that might be accessed and corrupted by other functions (see also §16.2.12).Initialization of a static local variable does not lead to a data race (§5.3.1) unless you enter thefunction containing it recursively or a deadlock occurs (§iso.6.7).
That is, the C++ implementationmust guard the initialization of a local static variable with some kind of lock-free construct (e.g., acall_once; §42.3.3). The effect of initializing a local static recursively is undefined. For example:Section 12.1.8int fn(int n){static int n1 = n;static int n2 = fn(n−1)+1;return n;}Local Variables315// OK// undefinedA static local variable is useful for avoiding order dependencies among nonlocal variables(§15.4.1).There are no local functions; if you feel you need one, use a function object or a lambda expression (§3.4.3, §11.4).The scope of a label (§9.6), should you be foolhardy enough to use one, is the complete function, independent of which nested scope it may be in.12.2 Argument PassingWhen a function is called (using the suffix (), known as the call operator or application operator),store is set aside for its formal arguments (also known as its parameters), and each formal argument is initialized by its corresponding actual argument.
The semantics of argument passing areidentical to the semantics of initialization (copy initialization, to be precise; §16.2.6). In particular,the type of an actual argument is checked against the type of the corresponding formal argument,and all standard and user-defined type conversions are performed. Unless a formal argument(parameter) is a reference, a copy of the actual argument is passed to the function. For example:int∗ find(int∗ first, int∗ last, int v)// find x in [first:last){while (first!=last && ∗first!=v)++first;return first;}void g(int∗ p, int∗ q){int∗ pp = find(p,q,'x');// ...}Here, the caller’s copy of the argument, p, is not modified by the operations on find()’s copy, calledfirst.
The pointer is passed by value.There are special rules for passing arrays (§12.2.2), a facility for passing unchecked arguments(§12.2.4), and a facility for specifying default arguments (§12.2.5). The use of initializer lists isdescribed in §12.2.3 and the ways of passing arguments to template functions in §23.5.2 and§28.6.2.316FunctionsChapter 1212.2.1 Reference ArgumentsConsider:void f(int val, int& ref){++val;++ref;}When f() is called, ++val increments a local copy of the first actual argument, whereasments the second actual argument. Consider:++refincre-void g(){int i = 1;int j = 1;f(i,j);}The call f(i,j) will increment j but not i.
The first argument, i, is passed by value; the second argument, j, is passed by reference. As mentioned in §7.7, functions that modify call-by-reference arguments can make programs hard to read and should most often be avoided (but see §18.2.5). It can,however, be noticeably more efficient to pass a large object by reference than to pass it by value. Inthat case, the argument might be declared a const reference to indicate that the reference is used forefficiency reasons only and not to enable the called function to change the value of the object:void f(const Large& arg){// the value of ‘‘arg’’ cannot be changed// (except by using explicit type conversion; §11.5)}The absence of const in the declaration of a reference argument is taken as a statement of intent tomodify the variable:void g(Large& arg);// assume that g() modifies argSimilarly, declaring a pointer argument const tells readers that the value of an object pointed to bythat argument is not changed by the function.
For example:int strlen(const char∗);char∗ strcpy(char∗ to, const char∗ from);int strcmp(const char∗, const char∗);// number of characters in a C-style string// copy a C-style string// compare C-style stringsThe importance of using const arguments increases with the size of a program.Note that the semantics of argument passing are different from the semantics of assignment.This is important for const arguments, reference arguments, and arguments of some user-definedtypes.Following the rules for reference initialization, a literal, a constant, and an argument thatrequires conversion can be passed as a const T& argument, but not as a plain (non-const) T& argument.
Allowing conversions for a const T& argument ensures that such an argument can be givenSection 12.2.1Reference Argumentsexactly the same set of values as aFor example:T317argument by passing the value in a temporary, if necessary.float fsqrt(const float&); // Fortran-style sqrt taking a reference argumentvoid g(double d){float r = fsqrt(2.0f);r = fsqrt(r);r = fsqrt(d);}// pass reference to temp holding 2.0f// pass reference to r// pass reference to temp holding static_cast<float>(d)Disallowing conversions for non-const reference arguments (§7.7) avoids the possibility of sillymistakes arising from the introduction of temporaries.
For example:void update(float& i);void g(double d, float r){update(2.0f);// error : const argumentupdate(r);// pass reference to rupdate(d);// error: type conversion required}Had these calls been allowed, update() would quietly have updated temporaries that immediatelywere deleted. Usually, that would come as an unpleasant surprise to the programmer.If we wanted to be precise, pass-by-reference would be pass-by-lvalue-reference because afunction can also take rvalue references. As described in §7.7, an rvalue can be bound to an rvaluereference (but not to an lvalue reference) and an lvalue can be bound to an lvalue reference (but notto an rvalue reference). For example:void f(vector<int>&);void f(const vector<int>&);void f(vector<int>&&);// (non-const) lvalue reference argument// const lvalue reference argument// rvalue reference argumentvoid g(vector<int>& vi, const vector<int>& cvi){f(vi);// call f(vector<int>&)f(vci);// call f(const vector<int>&)f(vector<int>{1,2,3,4}); // call f(vector<int>&&);}We must assume that a function will modify an rvalue argument, leaving it good only for destruction or reassignment (§17.5).