B. Stroustrup - The C++ Programming Language (794319), страница 61
Текст из файла (страница 61)
Forexample, we can compute an integer square root at compile time:constexpr int isqrt_helper(int sq, int d, int a){return sq <= a ? isqrt_helper(sq+d,d+2,a) : d;}constexpr int isqrt(int x){return isqrt_helper(1,3,x)/2 − 1;}constexpr int s1 = isqrt(9);constexpr int s2 = isqrt(1234);// s1 becomes 3The condition of a ?: is evaluated and then the selected alternative is evaluated. The alternative notselected is not evaluated and might even not be a constant expression.
Similarly, operands of &&and || that are not evaluated need not be constant expressions. This feature is primarily useful inconstexpr functions that are sometimes used as constant expressions and sometimes not.10.4.1 Symbolic ConstantsThe most important single use of constants (constexpr or const values) is simply to provide symbolic names for values. Symbolic names should be used systematically to avoid ‘‘magic numbers’’in code. Literal values scattered freely around in code is one of the nastiest maintenance hazards.If a numeric constant, such as an array bound, is repeated in code, it becomes hard to revise thatcode because every occurrence of that constant must be changed to update the code correctly.Using a symbolic name instead localizes information.
Usually, a numeric constant represents anassumption about the program. For example, 4 may represent the number of bytes in an integer,128 the number of characters needed to buffer input, and 6.24 the exchange factor between Danishkroner and U.S. dollars.
Left as numeric constants in the code, these values are hard for a maintainer to spot and understand. Also, many such values need to change over time. Often, suchnumeric values go unnoticed and become errors when a program is ported or when some otherchange violates the assumptions they represent. Representing assumptions as well-commentednamed (symbolic) constants minimizes such maintenance problems.10.4.2 consts in Constant ExpressionsA const is primarily used to express interfaces (§7.5).
However,constant values. For example:constcan also be used to expressSection 10.4.2constsin Constant Expressions265const int x = 7;const string s = "asdf";const int y = sqrt(x);A const initialized with a constant expression can be used in a constant expression. A const differsfrom a constexpr in that it can be initialized by something that is not a constant expression; in thatcase, the const cannot be used as a constant expression. For example:constexpr int xx = x;constexpr string ss = s;constexpr int yy = y;// OK// error : s is not a constant expression// error : sqr t(x) is not a constant expressionThe reasons for the errors are that string is not a literal type (§10.4.3) and sqrt() is not a constexprfunction (§12.1.6).Usually, constexpr is a better choice than const for defining simple constants, but constexpr isnew in C++11, so older code tends to use const.
In many cases, enumerators (§8.4) are anotheralternative to consts.10.4.3 Literal TypesA sufficiently simple user-defined type can be used in a constant expression. For example:struct Point {int x,y,z;constexpr Point up(int d) { return {x,y,z+d}; }constexpr Point move(int dx, int dy) { return {x+dx,y+dy}; }// ...};A class with a constexpr constructor is called a literal type. To be simple enough to be constexpr, aconstructor must have an empty body and all members must be initialized by potentially constantexpressions.
For example:constexpr Point origo {0,0};constexpr int z = origo.x;constexpr Point a[] = {origo, Point{1,1}, Point{2,2}, origo.move(3,3)};constexpr int x = a[1].x;// x becomes 1constexpr Point xy{0,sqrt(2)};// error : sqr t(2) is not a constant expressionNote that we can have constexpr arrays and also access array elements and object members.Naturally, we can define constexpr functions to take arguments of literal types. For example:constexpr int square(int x){return x∗x;}266ExpressionsChapter 10constexpr int radial_distance(Point p){return isqrt(square(p.x)+square(p.y)+square(p.z));}constexpr Point p1 {10,20,30};// the default constructor is constexprconstexpr p2 {p1.up(20)};// Point::up() is constexprconstexpr int dist = radial_distance(p2);I used int rather than double just because I didn’t have a constexpr floating-point square root function handy.For a member function constexpr implies const, so I did not have to write:constexpr Point move(int dx, int dy) const { return {x+dx,y+dy}; }10.4.4 Reference ArgumentsWhen working with constexpr, the key thing to remember is that constexpr is all about values.There are no objects that can change values or side effects here: constexpr provides a miniaturecompile-time functional programming language.
That said, you might guess that constexpr cannotdeal with references, but that’s only partially true because const references refer to values and cantherefore be used. Consider the specialization of the general complex<T> to a complex<double>from the standard library:template<> class complex<double> {public:constexpr complex(double re = 0.0, double im = 0.0);constexpr complex(const complex<float>&);explicit constexpr complex(const complex<long double>&);constexpr double real();void real(double);constexpr double imag();void imag(double);// read the real part// set the real part// read the imaginary par t// set the imaginary par tcomplex<double>& operator= (double);complex<double>& operator+=(double);// ...};Obviously, operations, such as = and +=, that modify an object cannot be constexpr. Conversely,operations that simply read an object, such as real() and imag(), can be constexpr and be evaluated atcompile time given a constant expression.
The interesting member is the template constructor fromanother complex type. Consider:constexpr complex<float> z1 {1,2};constexpr double re = z1.real();constexpr double im = z1.imag();constexpr complex<double> z2 {re,im};constexpr complex<double> z3 {z1};// note: <float> not <double>// z2 becomes a copy of z1// z3 becomes a copy of z1Section 10.4.4Reference Arguments267The copy constructor works because the compiler recognizes that the reference (the const complex<float>&) refers to a constant value and we just use that value (rather than trying anythingadvanced or silly with references or pointers).Literal types allow for type-rich compile-time programming.
Traditionally, C++ compile-timeevaluation has been restricted to using integer values (and without functions). This has resulted incode that was unnecessarily complicated and error-prone, as people encoded every kind of information as integers. Some uses of template metaprogramming (Chapter 28) are examples of that.Other programmers have simply preferred run-time evaluation to avoid the difficulties of writing inan impoverished language.10.4.5 Address Constant ExpressionsThe address of a statically allocated object (§6.4.2), such as a global variable, is a constant.
However, its value is assigned by the linker, rather than the compiler, so the compiler cannot know thevalue of such an address constant. That limits the range of constant expressions of pointer and reference type. For example:constexpr const char∗ p1 = "asdf";constexpr const char∗ p2 = p1;constexpr const char∗ p2 = p1+2;constexpr char c = p1[2];// OK// error : the compiler does not know the value of p1// OK, c==’d’; the compiler knows the value pointed to by p110.5 Implicit Type ConversionIntegral and floating-point types (§6.2.1) can be mixed freely in assignments and expressions.Wherever possible, values are converted so as not to lose information.
Unfortunately, some valuedestroying (‘‘narrowing’’) conversions are also performed implicitly. A conversion is value-preserving if you can convert a value and then convert the result back to its original type and get theoriginal value. If a conversion cannot do that, it is a narrowing conversion (§10.5.2.6). This section provides a description of conversion rules, conversion problems, and their resolution.10.5.1 PromotionsThe implicit conversions that preserve values are commonly referred to as promotions. Before anarithmetic operation is performed, integral promotion is used to create ints out of shorter integertypes.
Similarly, floating-point promotion is used to create doubles out of floats. Note that thesepromotions will not promote to long (unless the operand is a char16_t, char32_t, wchar_t, or a plainenumeration that is already larger than an int) or long double. This reflects the original purpose ofthese promotions in C: to bring operands to the ‘‘natural’’ size for arithmetic operations.The integral promotions are:• A char, signed char, unsigned char, short int, or unsigned short int is converted to an int if intcan represent all the values of the source type; otherwise, it is converted to an unsigned int.• A char16_t, char32_t, wchar_t (§6.2.3), or a plain enumeration type (§8.4.2) is converted tothe first of the following types that can represent all the values of its underlying type: int,unsigned int, long, unsigned long, or unsigned long long.268ExpressionsChapter 10•A bit-field (§8.2.7) is converted to an int if int can represent all the values of the bit-field;otherwise, it is converted to unsigned int if unsigned int can represent all the values of thebit-field.
Otherwise, no integral promotion applies to it.• A bool is converted to an int; false becomes 0 and true becomes 1.Promotions are used as part of the usual arithmetic conversions (§10.5.3).10.5.2 ConversionsThe fundamental types can be implicitly converted into each other in a bewildering number of ways(§iso.4). In my opinion, too many conversions are allowed. For example:void f(double d){char c = d;}// beware: double-precision floating-point to char conversionWhen writing code, you should always aim to avoid undefined behavior and conversions that quietly throw away information (‘‘narrowing conversions’’).A compiler can warn about many questionable conversions.
Fortunately, many compilers do.The {}-initializer syntax prevents narrowing (§6.3.5). For example:void f(double d){char c {d};}// error: double-precision floating-point to char conversionIf potentially narrowing conversions are unavoidable, consider using some form of run-timechecked conversion function, such as narrow_cast<>() (§11.5).10.5.2.1 Integral ConversionsAn integer can be converted to another integer type. A plain enumeration value can be converted toan integer type (§8.4.2) .If the destination type is unsigned, the resulting value is simply as many bits from the source aswill fit in the destination (high-order bits are thrown away if necessary).