概述
8.1 — Implicit type conversion (coercion)
What happens when a type conversion is invoked
When a type conversion is invoked (whether implicitly or explicitly), the compiler will determine whether it can convert the value from the current type to the desired type. If a valid conversion can be found, then the compiler will produce a new value of the desired type. Note that type conversions don’t change the value or type of the value or object being converted.
int x { 3.5 }; // brace-initialization disallows conversions that result in data loss
The standard conversions
8.2 — Floating-point and integral promotion
Numeric promotion
A numeric promotion is the type conversion of a narrower numeric type (such as a char) to a wider numeric type (typically int or double) that can be processed efficiently and is less likely to have a result that overflows.
Not all value-preserving conversions are numeric promotions
Some value-preserving type conversions (such as char to short, int to long, or int to double) are not considered to be numeric promotions in C++ (they are numeric conversions, which we’ll cover shortly in lesson 8.3 – Numeric conversions). This is because such conversions do not assist in the goal of converting smaller types to larger types that can be processed more efficiently.
The distinction is mostly academic. However, in certain cases, the compiler will favor numeric promotions over numeric conversions. We’ll see examples where this makes a difference when we cover function overload resolution (in upcoming lesson 8.11 – Function overload resolution and ambiguous matches).
8.3 — Numeric conversions
Narrowing conversions
Warning
Compilers will often not warn when converting a signed int to an unsigned int, or vice-versa, even though these are narrowing conversions. Be extra careful of inadvertent conversions between these types (particularly when passing an argument to a function taking a parameter of the opposite sign).
void someFcn(int i)
{
}
int main()
{
double d{ 5.0 };
someFcn(d); // bad: will generate compiler warning about narrowing conversion
someFcn(static_cast<int>(d)); // good: we're explicitly telling the compiler this narrowing conversion is expected, no warning generated
return 0;
}
Best practice
Avoid narrowing conversions whenever possible. If you do need to perform one, use static_cast to make it an explicit conversion.
8.4 — Arithmetic conversions
Signed and unsigned issues
8.5 — Explicit type conversion (casting) and static_cast
Type casting
Warning
Avoid const casts and reinterpret casts unless you have a very good reason to use them.
C-style casts
Best practice
Avoid using C-style casts.
static_cast
The main advantage of static_cast is that it provides compile-time type checking, making it harder to make an inadvertent error. static_cast is also (intentionally) less powerful than C-style casts, so you can’t inadvertently remove const or do other things you may not have intended to do.
Best practice
Favor static_cast when you need to convert a value from one type to another type.
8.6 — Typedefs and type aliases
Type aliases
In C++, using
is a keyword that creates an alias for an existing data type. To create such a type alias, we use the using keyword, followed by a name for the type alias, followed by an equals sign and an existing data type. For example:
using Distance = double; // define Distance as an alias for type double
Once defined, a type alias can be used anywhere a type is needed. For example, we can create a variable with the type alias name as the type:
Distance milesToDestination{ 3.4 }; // defines a variable of type double
Naming type aliases
Best practice
Name your type aliases starting with a capital letter and do not use a suffix (unless you have a specific reason to do otherwise).
Type aliases are not distinct types
Warning
Care must be taken not to mix values of aliases that are intended to be semantically distinct.
Typedefs
typedef int (*FcnType)(double, char); // FcnType hard to find
using FcnType = int(*)(double, char); // FcnType easier to find
Best practice
Prefer type aliases over typedefs.
Using type aliases to make complex types easier to read
This is probably the best use for type aliases.
Using type aliases for legibility
using TestScore = int;
TestScore gradeTest();
The return type of TestScore
makes it a little more obvious that the function is returning a type that represents a test score.
In our experience, creating a type alias just to document the return type of a single function isn’t worth it (use a comment instead). But if you have multiple functions passing or returning such a type, creating a type alias might be worthwhile.
Using type aliases for easier code maintenance
Downsides and conclusion
Best practice
Use type aliases judiciously, when they provide a clear benefit to code readability or code maintenance.
8.7 — Type deduction for objects using the auto keyword
Best practice
Use type deduction for your variables, unless you need to commit to a specific type.
8.8 — Type deduction for functions
Best practice
Favor explicit return types over function return type deduction for normal functions.
Trailing return type syntax
Type deduction can’t be used for function parameter types
8.9 — Introduction to function overloading
Introduction to function overloading
Key insight
Functions can be overloaded so long as each overloaded function can be differentiated by the compiler. If an overloaded function can not be differentiated, a compile error will result.
Introduction to overload resolution
Conclusion
Function overloading provides a great way to reduce the complexity of your program by reducing the number of function names you need to remember. It can and should be used liberally.
Best practice
Use function overloading to make your program simpler.
8.10 — Function overload differentiation
Type signature
A function’s type signature (generally called a signature) is defined as the parts of the function header that are used for differentiation of the function. In C++, this includes the function name, number of parameter, parameter type, and function-level qualifiers. It notably does not include the return type.
Name mangling
As an aside…
When the compiler compiles a function, it performs name mangling, which means the compiled name of the function is altered (“mangled”) based on various criteria, such as the number and type of parameters, so that the linker has unique names to work with.
For example, some function with prototype int fcn() might compile to name __fcn_v, whereas int fcn(int) might compile to name __fcn_i. So while in the source code, two overloaded functions share a name, in compiled code, the names are actually unique.
There is no standardization on how names should be mangled, so different compilers will produce different mangled names.
8.11 — Function overload resolution and ambiguous matches
The process of matching function calls to a specific overloaded function is called overload resolution.
Resolving overloaded function calls
The argument matching sequence
Key insight
Matches made by applying numeric promotions take precedence over any matches made by applying numeric conversions.
8.12 — Default arguments
A default argument is a default value provided for a function parameter. For example:
void print(int x, int y=10) // 10 is the default argument
{
std::cout << "x: " << x << 'n';
std::cout << "y: " << y << 'n';
}
Note that you must use the equals sign to specify a default argument. Using parenthesis or brace initialization won’t work:
void foo(int x = 5); // ok
void goo(int x ( 5 )); // compile error
void boo(int x { 5 }); // compile error
When to use default arguments
Default arguments are an excellent option when a function needs a value that has a reasonable default value, but for which you want to let the caller override if they wish.
For example, here are a couple of function prototypes for which default arguments might be commonly used:
int rollDie(int sides=6);
void openLogFile(std::string filename="default.log");
Author’s note
Because the user can choose whether to supply a specific argument value or use the default value, a parameter with a default value provided is sometimes called an optional parameter. However, the term optional parameter is also used to refer to several other types of parameters (including parameters passed by address, and parameters using std::optional), so we recommend avoiding this term.
Multiple default arguments
Rule
Default arguments can only be provided for the rightmost parameters.
Default arguments can not be redeclared
Best practice
If the function has a forward declaration (especially one in a header file), put the default argument there. Otherwise, put the default argument in the function definition.
Default arguments and function overloading
Functions with default arguments may be overloaded. For example, the following is allowed:
void print(std::string string)
{
}
void print(char ch=' ')
{
}
int main()
{
print("Hello, world"); // resolves to print(std::string)
print('a'); // resolves to print(char)
print(); // resolves to print(char)
return 0;
}
The function call to print() acts as if the user had explicitly called print(’ '), which resolves to print(char).
Now consider this case:
void print(int x);
void print(int x, int y = 10);
void print(int x, double y = 20.5);
Parameters with default values will differentiate a function overload (meaning the above will compile).
However, such functions can lead to potentially ambiguous function calls. For example:
print(1, 2); // will resolve to print(int, int)
print(1, 2.5); // will resolve to print(int, double)
print(1); // ambiguous function call
In the last case, the compiler is unable to tell whether print(1) should resolve to print(int) or one of the two functions where the second parameter has a default value. The result is an ambiguous function call.
8.13 — Function templates
Introduction to C++ templates
Key insight
The compiler can use a single template to generate a family of related functions or classes, each using a different set of types.
Key insight
Templates can work with types that didn’t even exist when the template was written. This helps make template code both flexible and future proof!
Function templates
A function template is a function-like definition that is used to generate one or more overloaded functions, each with a different set of actual types. This is what will allow us to create functions that can work with many different types.
Creating a templated max function
Here’s our new function that uses a single template type:
T max(T x, T y) // won't compile because we haven't defined T
{
return (x > y) ? x : y;
}
Best practice
Use a single capital letter (starting with T) to name your type template parameters (e.g. T, U, V, etc…)
This is a good start – however, it won’t compile because the compiler doesn’t know what T is! And this is still a normal function, not a function template.
Second, we’re going to tell the compiler that this is a function template, and that T is a type template parameter. This is done using what is called a template parameter declaration:
t
emplate <typename T> // this is the template parameter declaration
T max(T x, T y) // this is the function template definition for max<T>
{
return (x > y) ? x : y;
}
8.14 — Function template instantiation
Using a function template
Function templates are not actually functions – their code isn’t compiled or executed directly. Instead, function templates have one job: to generate functions (that are compiled and executed).
To use our max function template, we can make a function call with the following syntax:
max<actual_type>(arg1, arg2); // actual_type is some actual type, like int or double
This looks a lot like a normal function call – the primary difference is the addition of the type in angled brackets (called a template argument), which specifies the actual type that will be used in place of template type T.
So when we call max(1, 2), the function that gets instantiated looks something like this:
template<> // ignore this for now
int max<int>(int x, int y) // the generated function max<int>(int, int)
{
return (x > y) ? x : y;
}
Here’s the same example as above, showing what the compiler actually compiles after all instantiations are done:
#include <iostream>
// a declaration for our function template (we don't need the definition any more)
template <typename T>
T max(T x, T y);
template<>
int max<int>(int x, int y) // the generated function max<int>(int, int)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max<int>(1, 2) << 'n'; // instantiates and calls function max<int>(int, int)
return 0;
}
Template argument deduction
#include <iostream>
template <typename T>
T max(T x, T y)
{
std::cout << "called max<int>(int, int)n";
return (x > y) ? x : y;
}
int max(int x, int y)
{
std::cout << "called max(int, int)n";
return (x > y) ? x : y;
}
int main()
{
std::cout << max<int>(1, 2) << 'n'; // selects max<int>(int, int)
std::cout << max<>(1, 2) << 'n'; // deduces max<int>(int, int) (non-template functions not considered)
std::cout << max(1, 2) << 'n'; // calls function max(int, int)
return 0;
}
Best practice
Favor the normal function call syntax when using function templates.
Function templates with non-template parameters
Using function templates in multiple files
Generic programming
Because template types can be replaced with any actual type, template types are sometimes called generic types. And because templates can be written agnostically of specific types, programming with templates is sometimes called generic programming. Whereas C++ typically has a strong focus on types and type checking, in contrast, generic programming lets us focus on the logic of algorithms and design of data structures without having to worry so much about type information.
Conclusion
These drawbacks are fairly minor compared with the power and safety that templates bring to your programming toolkit, so use templates liberally anywhere you need type flexibility! A good rule of thumb is to create normal functions at first, and then convert them into function templates if you find you need an overload for different parameter types.
Best practice
Use function templates to write generic code that can work with a wide variety of types whenever you have the need.
8.15 — Function templates with multiple template types
This lack of type conversion is intentional for at least two reasons. First, it helps keep things simple: we either find an exact match between the function call arguments and template type parameters, or we don’t. Second, it allows us to create function templates for cases where we want to ensure that two or more parameters have the same type (as in the example above).
We’ll have to find another solution. Fortunately, we can solve this problem in (at least) three ways.
Use static_cast to convert the arguments to matching types
Provide an actual type
#include <iostream>
template <typename T>
T max(T x, T y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max<double>(2, 3.5) << 'n'; // we've provided actual type double, so the compiler won't use template argument deduction
return 0;
}
Functions templates with multiple template type parameters
#include <iostream>
template <typename T, typename U> // We're using two template type parameters named T and U
T max(T x, U y) // x can resolve to type T, and y can resolve to type U
{
return (x > y) ? x : y; // uh oh, we have a narrowing conversion problem here
}
int main()
{
std::cout << max(2, 3.5) << 'n';
return 0;
}
Because we’ve defined x with template type T, and y with template type U, x and y can now resolve their types independently. When we call max(2, 3.5), T can be an int and U can be a double. The compiler will happily instantiate max<int, double>(int, double) for us.
However, the above code still has a problem: using the usual arithmetic rules (8.4 – Arithmetic conversions), double takes precedence over int, so our conditional operator will return a double. But our function is defined as returning a T – in cases where T resolves to an int, our double return value will undergo a narrowing conversion to an int, which will produce a warning (and possible loss of data).
Making the return type a U instead doesn’t solve the problem, as we can always flip the order of the operands in the function call to flip the types of T and U.
How do we solve this? This is a good use for an auto
return type – we’ll let the compiler deduce what the return type should be from the return statement:
#include <iostream>
template <typename T, typename U>
auto max(T x, U y)
{
return (x > y) ? x : y;
}
int main()
{
std::cout << max(2, 3.5) << 'n';
return 0;
}
This version of max
now works fine with operands of different types.
Key insight
Each template type parameter will resolve its type independently.
This means a template with two type parameters T and U could have T and U resolve to distinct types, or they could resolve to the same type.
Abbreviated function templates C++20
C++20 introduces a new use of the auto keyword: When the auto keyword is used as a parameter type in a normal function, the compiler will automatically convert the function into a function template with each auto parameter becoming an independent template type parameter. This method for creating a function template is called an abbreviated function template.
For example:
auto max(auto x, auto y)
{
return (x > y) ? x : y;
}
is shorthand in C++20 for the following:
template <typename T, typename U>
auto max(T x, U y)
{
return (x > y) ? x : y;
}
which is the same as the max function template we wrote above.
Best practice
Feel free to use abbreviated function templates with a single auto parameter, or where each auto parameter should be an independent type (and your language standard is set to C++20 or newer).
8.x — Chapter 8 summary and quiz
The auto keyword has a number of uses. First, auto can be used to do type deduction (also called type inference), which will deduce a variable’s type from its initializer. Type deduction drops const and references, so be sure to add those back if you want them.
Auto can also be used as a function return type to have the compiler infer the function’s return type from the function’s return statements, though this should be avoided for normal functions. Auto is used as part of the trailing return syntax.
In C++20, when the auto keyword is used as a parameter type in a normal function, the compiler will automatically convert the function into a function template with each auto parameter becoming an independent template type parameter. This method for creating a function template is called an abbreviated function template.
最后
以上就是自觉芝麻为你收集整理的Type Conversion and Function Overloading___CH_88.1 — Implicit type conversion (coercion)8.2 — Floating-point and integral promotion8.3 — Numeric conversions8.4 — Arithmetic conversions8.5 — Explicit type conversion (casting) and static_cast8.6 — Type的全部内容,希望文章能够帮你解决Type Conversion and Function Overloading___CH_88.1 — Implicit type conversion (coercion)8.2 — Floating-point and integral promotion8.3 — Numeric conversions8.4 — Arithmetic conversions8.5 — Explicit type conversion (casting) and static_cast8.6 — Type所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复