B. Stroustrup - The C++ Programming Language (794319), страница 68
Текст из файла (страница 68)
Forexample:double (∗p1)(double) = [](double a) { return sqrt(a); };double (∗p2)(double) = [&](double a) { return sqrt(a); };double (∗p3)(int) = [](int a) { return sqrt(a); };// error : the lambda captures// error : argument types do not match11.5 Explicit Type ConversionSometimes, we have to convert a value of one type into a value of another. Many (arguably toomany) such conversions are done implicitly according to the language rules (§2.2.2, §10.5). Forexample:double d = 1234567890; // integer to floating-pointint i = d;// floating-point to integerIn other cases, we have to be explicit.For logical and historical reasons, C++ offers explicit type conversion operations of varyingconvenience and safety:• Construction, using the {} notation, providing type-safe construction of new values (§11.5.1)• Named conversions, providing conversions of various degrees of nastiness:• const_cast for getting write access to something declared const (§7.5)• static_cast for reversing a well-defined implicit conversion (§11.5.2)• reinterpret_cast for changing the meaning of bit patterns (§11.5.2)• dynamic_cast for dynamically checked class hierarchy navigation (§22.2.1)• C-style casts, providing any of the named conversions and some combinations of those(§11.5.3)• Functional notation, providing a different notation for C-style casts (§11.5.4)I have ordered these conversions in my order of preference and safety of use.Except for the {} construction notation, I can’t say I like any of those, but at least dynamic_castis run-time checked.
For conversion between two scalar numeric types, I tend to use a homemadeexplicit conversion function, narrow_cast, where a value might be narrowed:Section 11.5Explicit Type Conversion299template<class Target, class Source>Target narrow_cast(Source v){auto r = static_cast<Target>(v);// convert the value to the target typeif (static_cast<Source>(r)!=v)throw runtime_error("narrow_cast<>() failed");return r;}That is, if I can convert a value to the target type, convert the result back to the source type, and getback the original value, I’m happy with the result.
That is a generalization of the rule the languageapplies to values in {} initialization (§6.3.5.2). For example:void test(double d, int i, char∗ p){auto c1 = narrow_cast<char>(64);auto c2 = narrow_cast<char>(−64);auto c3 = narrow_cast<char>(264);// will throw if chars are unsigned// will throw if chars are 8-bit and signedauto d1 = narrow_cast<double>(1/3.0F); // OKauto f1 = narrow_cast<float>(1/3.0);// will probably throwauto c4 = narrow_cast<char>(i);auto f2 = narrow_cast<float>(d);// may throw// may throwauto p1 = narrow_cast<char∗>(i);auto i1 = narrow_cast<int>(p);// compile-time error// compile-time errorauto d2 = narrow_cast<double>(i);auto i2 = narrow_cast<int>(d);// may throw (but probably will not)// may throw}Depending on your use of floating-point numbers, it may be worthwhile to use a range test forfloating-point conversions, rather than !=.
That is easily done using specializations (§25.3.4.1) ortype traits (§35.4.1).11.5.1 ConstructionThe construction of a value of type(§iso.8.5.4). For example:auto d1 = double{2};double d2 {double{2}/4};Part of the attraction of theFor example:Tfrom a valueecan be expressed by the notationT{e}// d1==2.0// d1==0.5T{v}notation is that it will perform only ‘‘well-behaved’’ conversions.300Select OperationsChapter 11void f(int);void f(double);void g(int i, double d){f(i);f(double{i});// call f(int)// error : {} doesn’t do int to floating conversionf(d);f(int{d});f(static_cast<int>(d));// call f(double)// error : {} doesn’t truncate// call f(int) with a truncated valuef(round(d));// call f(double) with a rounded valuef(static_cast<int>(lround(d))); // call f(int) with a rounded value// if the d is overflows the int, this still truncates}I don’t consider truncation of floating-point numbers (e.g., 7.9 to 7) ‘‘well behaved,’’ so having tobe explicit when you want it is a good thing.
If rounding is desirable, we can use the standardlibrary function round(); it performs ‘‘conventional 4/5 rounding,’’ such as 7.9 to 8 and 7.4 to 7.It sometimes comes as a surprise that {}-construction doesn’t allow int to double conversion, butif (as is not uncommon) the size of an int is the same as the size of a double, then some such conversions must lose information.
Consider:static_assert(sizeof(int)==sizeof(double),"unexpected sizes");int x = numeric_limits<int>::max(); // largest possible integerdouble d = x;int y = x;We will not get x==y. However, we can still initialize arepresented exactly. For example:double d { 1234 };doublewith an integer literal that can be// fineExplicit qualification with the desired type does not enable ill-behaved conversions.
For example:void g2(char∗ p){int x = int{p};using Pint = int∗;int∗ p2 = Pint{p};// ...}// error: no char* to int conversion// error: no char* to int* conversionFor T{v}, ‘‘reasonably well behaved’’ is defined as having a ‘‘non-narrowing’’ (§10.5) conversionfrom v to T or having an appropriate constructor for T (§17.3).The constructor notation T{} is used to express the default value of type T.
For example:Section 11.5.1Construction301template<class T> void f(const T&);void g3(){f(int{});f(complex<double>{});// ...}// default int value// default complex valueThe value of an explicit use of the constructor for a built-in type is 0 converted to that type (§6.3.5).Thus, int{} is another way of writing 0.
For a user-defined type T, T{} is defined by the default constructor (§3.2.1.1, §17.6), if any, otherwise by default construction, MT{}, of each member.Explicitly constructed unnamed objects are temporary objects, and (unless bound to a reference)their lifetime is limited to the full expression in which they are used (§6.4.2). In this, they differfrom unnamed objects created using new (§11.2).11.5.2 Named CastsSome type conversions are not well behaved or easy to type check; they are not simple constructions of values from a well-defined set of argument values.
For example:IO_device∗ d1 = reinterpret_cast<IO_device∗>(0Xff00); // device at 0Xff00There is no way a compiler can know whether the integer 0Xff00 is a valid address (of an I/O deviceregister). Consequently, the correctness of the conversions is completely in the hands of the programmer. Explicit type conversion, often called casting, is occasionally essential. However, traditionally it is seriously overused and a major source of errors.Another classical example of the need for explicit type conversion is dealing with ‘‘raw memory,’’ that is, memory that holds or will hold objects of a type not known to the compiler.
Forexample, a memory allocator (such as operator new(); §11.2.3) may return a void∗ pointing to newlyallocated memory:void∗ my_allocator(size_t);void f(){int∗ p = static_cast<int∗>(my_allocator(100));// ...}// new allocation used as intsA compiler does not know the type of the object pointed to by the void∗.The fundamental idea behind the named casts is to make type conversion more visible and toallow the programmer to express the intent of a cast:• static_cast converts between related types such as one pointer type to another in the sameclass hierarchy, an integral type to an enumeration, or a floating-point type to an integraltype.
It also does conversions defined by constructors (§16.2.6, §18.3.3, §iso.5.2.9) andconversion operators (§18.4).302Select OperationsChapter 11•reinterpret_cast handles conversions between unrelated types such as an integer to a pointeror a pointer to an unrelated pointer type (§iso.5.2.10).• const_cast converts between types that differ only in const and volatile qualifiers(§iso.5.2.11).• dynamic_cast does run-time checked conversion of pointers and references into a class hierarchy (§22.2.1, §iso.5.2.7).These distinctions among the named casts allow the compiler to apply some minimal type checkingand make it easier for a programmer to find the more dangerous conversions represented as reinterpret_casts. Some static_casts are portable, but few reinterpret_casts are.