This rule is part of MISRA C++:2023.
Usage of this content is governed by Sonar’s terms and conditions. Redistribution is
prohibited.
Rule 7.0.6 - Assignment between numeric types shall be appropriate
[conv]
Category: Required
Analysis: Decidable,Single Translation Unit
Amplification
This rule applies to all assignments where the source and target have numeric type.
A call is non-extensible when it is:
- A qualified call to a member function (such as
a.f( x ), this->f( x ), or A::f(x)); or
- A call to an
operator().
A function argument arg is overload-independent when the call is:
- Through a pointer to function or pointer to member function; or
- Non-extensible, and, for all overloads that are callable with the same number of arguments, the parameters corresponding to
arg have the same type. Note that a parameter of a function template that is dependent on a function template parameter never has the
same type.
The source and target within an assignment shall have the same type when the source expression is:
- An argument to a function call (including an implicit constructor call) and the corresponding parameter is not overload-independent;
or
- Passed through the ellipsis parameter in a function call (where the target type is the promoted type of the argument).
For all other assignments:
- The source and target shall have types of the same type category, signedness and size; or
- The source and target shall have types of the same type category, signedness, the source size shall be smaller than the target size,
and the source shall be an id-expression; or
- The source shall be an integer constant expression and the target shall be either:
- Any numeric type with a range large enough to represent the value, even if the value is not exactly representable (when storing to
a float, for example); or
- A bit-field whose value representation width (see [class.bit]/1) and signedness are capable of representing the value.
Rationale
The C++ built-in operators perform many implicit conversions on their operands. These conversions can lead to unexpected information loss, change
of signedness, implementation-defined behaviour or undefined behaviour. This rule therefore places restrictions on the presence of
implicit numeric conversions on assignment.
For floating-point types, the exact representation of a value is often not possible, so loss of precision when assigning a constant value is not a
violation of this rule, provided it is within the range of the target type.
Additionally, implicit conversions on assignment to a function parameter are undesirable as they could result in a silent change in
overload selection due to changes elsewhere within the code, such as the addition of a #include. For this reason, the implicit conversion
of a function argument is not permitted — except when the corresponding parameter is overload-independent, in which case an implicit
conversion of the type category is permitted as a silent change in overload selection cannot occur.
Exception
The assignment to a parameter within a call to a constructor that is callable with a single numeric argument is permitted to have
a target type that is a wider version of the source type, provided that the class has no other constructors that are callable with a single argument,
apart from copy or move constructors. This allows an instance of the class to be created and used as a function parameter without requiring an
explicit widening conversion of the source type.
Example
u32 = 1; // Compliant
s32 = 4u * 2u; // Compliant
u8 = 3u; // Compliant
u8 = 3_u8; // Compliant
u8 = 300u; // Non-compliant - value does not fit
The use of bit-fields in the following example violates M23_159: MISRA C++ 2023 Rule 12.2.1.
struct S { uint32_t b : 2; } s; // Bit-field is considered to be uint8_t
s.b = 2; // Compliant
s.b = 32u; // Non-compliant - value does not fit
s.b = u8; // Compliant - same width, but may truncate
s.b = u16; // Non-compliant - narrowing
void sb1( uint32_t );
void sb1( uint8_t );
void sb2( uint8_t );
void sb3()
{
sb1( s.b ); // Non-compliant - s.b considered to be uint8_t,
// but sb1( uint32_t ) is called
sb2( s.b ); // Compliant
}
enum Colour : uint16_t
{
red, green, blue
} c;
u8 = red; // Compliant - value can be represented
u32 = red; // Compliant - value can be represented
u8 = c; // Non-compliant - different sizes (narrowing)
u32 = c; // Compliant - widening of id-expression
enum States
{
enabled, disabled
};
u8 = enabled; // Rule does not apply - source type not numeric
unsigned long ul;
unsigned int ui = ul; // Compliant - if sizes are equal
u8 = s8; // Non-compliant - different signedness
u8 = u8 + u8; // Non-compliant - change of sign and narrowing
flt1 = s32; // Non-compliant - different type category
flt2 = 0.0; // Non-compliant - different sizes and not an
// integral constant expression
flt3 = 0.0f; // Compliant
flt4 = 1; // Compliant
flt5 = 9999999999; // Compliant - loss of precision is possible
int f( int8_t s8 )
{
int16_t val1 = s8; // Compliant
int16_t val2 { s8 }; // Compliant
int16_t val3 ( s8 ); // Compliant
int16_t val4 { s8 + s8 }; // Non-compliant - narrowing, as s8 + s8 is int
switch ( s8 )
{
case 1: // Compliant
case 0xFFFF'FFFF'FFFF: // Non-compliant - value does not fit in int8_t
return s8; // Compliant - widening of id-expression
}
return s8 + s8; // Compliant - s8 + s8 is of type int
}
The following examples demonstrate the assignment to function parameters that are not overload-independent:
void f1( int64_t i );
f1( s32 + s32 ); // Non-compliant - implicit widening conversion
void f2( int i );
f2( s32 + s64 ); // Non-compliant - implicit narrowing conversion
f2( s16 + s16 ); // Compliant - result of addition is int
struct A
{
explicit A( int32_t i );
explicit A( int64_t i );
};
A a { s16 }; // Non-compliant
void f3( long l );
void ( *fp )( long l) = &f3;
f3( 2 ); // Non-compliant - implicit conversion from int to
// long. Adding a #include would silently change
// the selected overload if it added void f3( int )
fp( 2 ); // Compliant - calling through function pointer is
// overload-independent
struct MyInt
{
explicit MyInt( int32_t );
MyInt( int32_t, int32_t );
};
void f4( MyInt );
void bar ( int16_t s )
{
f4( MyInt { s } ); // Compliant by exception - no need to cast s
MyInt i { s }; // Compliant by exception - no need to cast s
}
void log( char const * fmt, ... );
void f( uint8_t c )
{
log( "f( %c )", c ); // Non-compliant - conversion of c from uint8_t
} // to int
In the following example, all overloads of the function A::set that can be called with two arguments have the type size_t
for their first parameter. Therefore, the first parameter in a qualified call to A::set is overload-independent:
struct A
{
void set( short value );
void set( size_t index, int value );
void set( size_t index, std::string value );
void set( int index, double value ) = delete; // Not callable
void g();
};
void f( A & a )
{
a.set( 42, "answer" ); // Compliant - size_t can represent 42, and it is
} // assigned to an overload-independent parameter
void A::g()
{
set( 42, "answer" ); // Non-compliant - even though this non-qualified
// call will only select an overload in the class
}
In the following example, both overloads of the function B::set can be called with two arguments, but their first parameters do not
have the same type (even if int and long have the same size). Therefore, the first parameter in a qualified call to
B::set is not overload-independent:
struct B
{
void set( int index, int value );
void set( long index, std::string value );
};
void f( B & b )
{
b.set( 42, "answer" ); // Non-compliant - conversion from int to long not
} // allowed as parameter is not overload-independent
struct C
{
int32_t x;
int64_t y;
int64_t z;
};
C c1 {
s16 + s16, // Compliant - s16 + s16 is of type int
s16 + s16, // Non-compliant - widening from int
s16 // Compliant - widening of id-expression
};
template< typename T >
struct D
{
void set1( T index, int value );
void set1( T index, std::string value );
template< typename S1 > void set2( S1 index, int value );
template< typename S2 > void set2( S2 index, std::string value );
};
void f( D< size_t > & a )
{
a.set1( 42, "X" ); // Compliant - size_t is same type
a.set2< size_t >( 42, "X" ); // Non-compliant - 'S1' is never the same as
} // the specialized type of 'S2' (size_t)
Copyright The MISRA Consortium Limited © 2023