C++ is a statically typed language. In some ways, C++ is both strongly and weakly typed, as it requires all objects/expressions to have a type, but allows for some implicit conversions of values from one type to another type. The following cases require special consideration:
Most notably are the implicit conversions between arithmetic types (including bool
) that silently might truncate values or have loss of precision (for details see Conversion Errors [FLC]). This shared vulnerability includes integral promotion from small (unsigned) integer types (e.g. unsigned char
) to the signed integer type int
that can lead to operations with undefined behavior [EWF], where unsigned arithmetic is defined with modulo semantics.
The standard types used to represent text characters ( char
, char8_t
, wchar_t
, char16_t
, char32_t
) are treated as arithmetic types even when used to represent text and thus are subject to implicit conversions to and from other arithmetic types. Using values of character types in arithmetic expressions, especially together with other types can be confusing. Furthermore, using values of such types or sequences of such values with their corresponding string types with relational operators might not provide an expected sorting order, depending on the language or the encoding the text represents. Libraries for text processing might take such locale-specific or text-representation-specific behavior into account, whereas the built-in operators of the language don’t.
C++ considers implicit conversions during function overload resolution, when there is not a direct match of argument type with parameter type. Sometimes such conversions lead to ambiguities, which result in a compile error. But there can also be a single best matching overload that does not correspond to developer’s expectation including conversion of argument types (see Identifier Name Reuse [YOW]). Even where the code presently corresponds to programmer expectation, a later addition of code can create a new “better match”, potentially resulting in a silent change of behaviour (see Namespace issues [BJL], and Conversion Errors [FLC]).
Values of an unscoped enumeration convert to their underlying integral type in arithmetic expressions (see Enumerator Issues [CCB]).
Built-in array types as well as function types will decay to a pointer type. For array types this is problematic, because the array’s extent is lost (see Unchecked Array Indexing [XYZ] and Pointer Arithmetic [RVG])
Pointer types implicitly convert to pointer-to-void (see Pointer Type Conversions [HFC]).
Pointer and reference types referring a derived class object can implicitly convert to a pointer or reference to one of its public base classes respectively (see Polymorphic Variables [BKK]).
User-defined class types can define conversion operators and converting constructors that can be called with a single argument. If these member functions are not declared with the keyword explicit
they are eligible for use in implicit conversions.
Implicit conversions from an arithmetic type to bool
often indicate a missing comparison. Conversions from bool
in arithmetic expression can obfuscate code. For example, the following code relies on false
converting to/from zero and true
to one and from non-zero:
double fluxcompensation(double flux, bool compensate){
if (flux) { // double to bool conversion
double delta = compute_delta();
double const compensate_v = 1.4;
return flux + delta * compensate; // bool to double conversion
} return 1.;
}
explicit operator bool()
conversion operator. These conversions are idiomatic C++ in boolean contexts; however, some prefer explicit tests to show intent, for example, if (p) { *p = 42; }
vs. if (p != nullptr) { *p = 42; }
.Note that type aliases (using
, typedef
) do not define a different type from their alias just a different name and thus do not incur any conversion between the alias and the aliased type.
Instead of using the built-in arithmetic types or generic library types such as std::string
for your domain values, C++ allows to wrap them in user-defined-class types as so-called strong types. For integral values, enum class
types can also be used. Strong types provide only those operator overloads and conversions for each such type that make sense in the application domain. User-defined-literal operators help with providing constants of appropriate strong types. Such strong types provide full control of conversions and operations available, avoiding semantically unsound operations that the built-in or other generic types might provide.
For example, a very simple strong type representation of temperature values can be implemented as follows:
struct Celsius {
double value;
};struct Fahrenheit {
double value;
};
Fahrenheit convert_to_fahrenheit(Celsius c){return { 9*c.value/5+32};
}//...
20.}); // doesn't compile Celsius wrong = convert_to_fahrenheit({
In a realistic scenario using a library for strong type support eases the definition and use of strong types.
To avoid the vulnerability or mitigate its ill effects, C++ software developers can: - Use the avoidance mechanisms of ISO/IEC TR 24772-1:2019, 6.2.5. and the guidance provided in the different related sections of this document.
Be aware of the rules of the type system, overload resolution, and implicit conversions to avoid vulnerabilities.
Enable compiler warnings regarding implicit conversions and/or use static analysis tools that provide such warnings.
Define any constructor of a class that can be called with a single argument of a different type as explicit
.
Define any conversion operator as explicit
Use strong types for domain values instead of the built-in types. On system boundaries, e.g., for input, convert a read value immediately to the appropriate strong type.
When defining variables of arithmetic type, use a braced-initializer to prevent a potential narrowing conversion from the initial value’s type.
Avoid the use of the following text-representing character types, char
, char8_t
, wchar_t
, char16_t
and char32_t
, in arithmetic or relational expressions. Use an appropriate text processing library instead for character classification, conversion, comparison, and further processing.
Use a consistent style in a project with respect to implicit pointer conversions in boolean contexts.