auto
2.1 Prefer auto to Explicit Type Declarations
auto:
A type specifier that lets the compiler deduce a variable's type from its initializer.
Type Inference:
The process of determining a type automatically from the surrounding program context.
Initializer Requirement:
A variable declared with auto must have an initializer so that its type can be deduced.
1) Automatic Type Deduction
Automatic Type Deduction:
Allows the compiler to determine a variable's type instead of requiring the programmer to write it explicitly.
Initialization Safety:
Because auto requires an initializer, it prevents declarations of uninitialized auto variables.
Complex Type Simplification:
auto avoids explicitly writing types that are long, complicated, or dependent on templates.
Compiler-known Type:
A type that can be determined by the compiler but may be difficult or impossible for the programmer to write directly.
Closure Type:
The unique compiler-generated type of an object created from a lambda expression.
Generic Lambda:
A C++14 lambda whose parameters can use auto, allowing their types to be deduced when the lambda is called.
2) auto and Callable Objects
Callable Object:
An object that can be invoked using function-call syntax.
std::function:
A standard library class template that can store and invoke compatible callable objects.
Function Signature:
The parameter and return-type information describing how a callable object is invoked.
Closure Storage with auto:
Using auto to store a lambda preserves the lambda's actual closure type.
Closure Storage with std::function:
Storing a lambda in std::function converts it into a general-purpose callable wrapper with a specified function signature.
Storage Overhead:
A std::function object may require more memory than storing the closure directly with auto.
Heap Allocation:
std::function may allocate dynamic memory when its internal storage is insufficient for the callable object.
Invocation Overhead:
Calling through std::function may involve indirect calls and can inhibit optimizations such as inlining.
3) Avoiding Type Mismatches
Type Mismatch:
A difference between the explicitly declared type of a variable and the actual type produced by its initializer.
Implicit Conversion:
A conversion automatically performed by the compiler when two types do not directly match.
Type Shortcut:
Using a simpler explicit type in place of the exact type produced by an expression.
Container size_type:
The implementation-appropriate unsigned integer type used to represent a container's size.
Portability Problem:
A problem caused by assumptions about types or sizes that may differ between platforms.
auto Type Matching:
Using auto ensures that the variable's type is derived directly from the initializer rather than from a manually chosen approximation.
4) Container Element Types
Associative Container Element:
An element stored by an associative container according to the container's required key and value types.
std::unordered_map Element Type:
An element of std::unordered_map<Key, T> has a key that is const, so its stored pair type contains const Key.
Unintended Temporary:
A temporary object that may be created when an explicitly declared type does not exactly match the actual element type.
Reference Binding:
A reference binds directly to an object only when the required type rules are satisfied; otherwise a conversion may create a temporary object.
const auto&:
A declaration that deduces the exact element type while accessing it through a non-modifying reference.
5) Refactoring
Refactoring:
Changing the structure or implementation of code without intentionally changing its behavior.
Type Adaptability:
An auto variable automatically adapts when the type of its initializer changes.
Explicit Type Maintenance:
Explicit declarations may need to be manually updated when the type produced by an expression changes.
6) Appropriate Use of auto
Prefer auto:
Use auto when it prevents unnecessary type repetition, avoids type mismatches, or makes code easier to maintain.
Explicit Type Declaration:
May still be preferable when writing the type directly makes the code clearer or better communicates intent.
Deduction Limitation:
auto is only as appropriate as the type produced by its initializer; some expressions produce types that are undesirable to store directly.
2.2 Explicitly Typed Initializer Idiom
Explicitly Typed Initializer Idiom:
A technique that uses auto together with an explicit conversion so that the compiler deduces the intended type.
Undesired Type Deduction:
Occurs when auto correctly deduces the initializer's actual type, but that type is not the type the programmer intends to store.
1) Proxy Classes
Proxy Class:
A class designed to represent, emulate, or augment the behavior of another type.
Proxy Object:
An object of a proxy class that stands in for another value or object.
Invisible Proxy:
A proxy type designed to behave like another type without being obvious to the caller.
Proxy Conversion:
An implicit conversion that allows a proxy object to behave like the type it represents.
2) std::vector<bool>::reference
std::vector<bool>:
A specialization of std::vector that may store boolean values in a packed bit representation.
Bit-packed Representation:
A storage representation in which individual boolean values occupy bits rather than separate bool objects.
Bit Reference Limitation:
C++ references cannot directly refer to individual bits.
std::vector<bool>::reference:
A proxy type returned by std::vector<bool>::operator[] that behaves similarly to a reference to a boolean value.
Implicit bool Conversion:
std::vector<bool>::reference can be converted to bool when an actual boolean value is required.
3) auto with Proxy Types
Proxy Type Deduction:
auto deduces the actual proxy type returned by an expression rather than the conceptual type represented by the proxy.
Proxy Lifetime:
Some proxy objects are intended to exist only briefly as part of an expression.
Temporary Object:
An unnamed object whose lifetime commonly ends at the end of the full expression that created it.
Dangling Pointer:
A pointer that refers to storage whose lifetime has already ended.
Dangling Proxy:
A proxy object whose internal reference or pointer refers to an object that no longer exists.
Undefined Behavior:
Program behavior for which the C++ language imposes no requirements.
4) Detecting Proxy Types
Library Documentation:
Documentation may identify interfaces that return proxy objects instead of ordinary values or references.
Function Signature:
The declared return type of a function can reveal that an operation returns a proxy type.
Interface Inspection:
Examining library declarations can determine the actual type returned by an expression.
Unexpected Return Type:
A return type different from the expected value or reference type can indicate the use of a proxy class.
5) Expression Templates
Expression Template:
A technique that represents an expression using temporary template-based proxy objects instead of immediately computing the final result.
Expression Proxy:
An intermediate object representing part or all of an expression.
Deferred Evaluation:
Delays computation until the final result is required.
Numeric Optimization:
Expression templates can reduce unnecessary intermediate objects and improve the efficiency of numerical expressions.
6) Forcing the Desired Type
Explicit Conversion:
A conversion explicitly requested by the programmer rather than automatically performed by the compiler.
static_cast:
A C++ cast used to request supported compile-time checked conversions between types.
Explicitly Typed Initializer:
An initializer explicitly converted to the desired type before auto performs type deduction.
auto value = static_cast<T>(expression);
auto then deduces T because the converted initializer expression has type T.
7) Converting Proxy Types
Proxy-to-Value Conversion:
Explicitly converts a proxy object into the value type it represents.
auto highPriority =
static_cast<bool>(features(w)[5]);
The proxy is converted to bool before the temporary object on which it depends is destroyed.
8) Intentional Type Conversion
Intentional Conversion:
A conversion deliberately requested because the destination type better matches the program's requirements.
Precision Reduction:
Deliberately converting a value to a type with lower numerical precision.
auto ep = static_cast<float>(calcEpsilon());
Floating-point-to-Integer Conversion:
Deliberately converting a floating-point result to an integer type.
auto index = static_cast<int>(d * c.size());
Conversion Intent:
An explicit cast documents that the difference between the initializer's original type and the variable's final type is intentional.
Explicitly Typed Initializer Rules
Normal Case:
Prefer ordinary auto when the initializer already has the desired type.
Proxy Case:
Do not blindly store an invisible proxy type when the intended result is the represented value.
Desired Different Type:
Use an explicitly typed initializer when the variable should intentionally have a type different from the initializer's original type.
General Form:
auto variable = static_cast<DesiredType>(expression);