Is it safe to assign bool or float to int32_t? - c++

I have a c++ project that have a struct with int32_t member data type and i want to assign bool and float variables to it, will that ruin the value of the variable? If yes, what should i do other than changing the struct member data type?

Whenever the compiler has to use a value of a type in a context where another type is expected, an implicit conversion is performed.
The rules for implicit conversions are numerous, but under Floating–integral conversions, there is the following paragraph:
A prvalue of floating-point type can be converted to a prvalue of any
integer type. The fractional part is truncated, that is, the
fractional part is discarded. If the value cannot fit into the
destination type, the behavior is undefined (even when the destination
type is unsigned, modulo arithmetic does not apply). If the
destination type is bool, this is a boolean conversion (see below).
So, you can safely assign a floating point type (e.g. float or double) to an integer. Provided that the value can fit, the decimal part will be truncated. This implies a loss of data, and might be a source of bugs, or might be done on purpose in certain applications. Note that the floating point types have a larger range than int32_t, and if the value cannot be stored, it is undefined behaviour as per the standard.
Bools, on the other hand, can be safely assigned to integer types under all circumstances:
If the source type is bool, the value false is converted to zero and
the value true is converted to the value one of the destination type
(note that if the destination type is int, this is an integer
promotion, not an integer conversion).

The value you assign will be converted to int32_t. The bool value will not lose anything. The float value will be truncated. Only the integral part of it will be stored.
If yes, what should i do other than changing the struct member data type?
That depends on whether the type of the values you want to store are determined at runtime or compile-time. If it's determined at runtime, you can use an std::any instead of an int32_t:
#include <any>
struct MyStruct {
std::any val;
};
// ...
MyStruct s;
s.val = true; // val now contains a bool
s.val = 3.1415; // val now contains a double
s.val = 3.1415f; // val now contains a float
s.val = 42; // val now contains an int
If the type is determined at compile-time, you can make a struct template:
template <typename T>
struct MyStruct {
T val;
};
// ...
MyStruct<bool> s1;
s1 = false;
MyStruct<float> s2;
s2 = 3.1415;

Related

Bit operations with integer promotion

tl;dr Is bit manipulation safe and behaving as expected when it goes through integer promotion (with types shorter than int)?
e.g.
uint8_t a, b, c;
a = b & ~c;
This is a rough MCVE of what I have:
struct X { // this is actually templated
using U = unsigned; // U is actually a dependent name and can change
U value;
};
template <bool B> auto foo(X x1, X x2) -> X
{
if (B)
return {x1.value | x2.value};
else
return {x1.value & ~x2.value};
}
This works great, but when U is changed to a integer type shorter than int, e.g. std::uint8_t then due to integer promotions I get a warning:
warning: narrowing conversion of '(int)(((unsigned
char)((int)x1.X::value)) | ((unsigned char)((int)x2.X::value)))' from
'int' to 'X::U {aka unsigned char}' inside { } [-Wnarrowing]
So I added a static_cast:
struct X {
using U = std::uint8_t;
U value;
};
template <bool B> auto foo(X x1, X x2) -> X
{
if (B)
return {static_cast<X::U>(x1.value | x2.value)};
else
return {static_cast<X::U>(x1.value & ~x2.value)};
}
The question: Can the integer promotion and then the narrowing cast mess with the intended results (*)? Especially since these are casts change signedness back and forward (unsigned char -> int -> unsigned char). What about if U is signed, i.e. std::int8_t (it won't be signed in my code, but curious about the behavior if it would be).
My common sens says the code is perfectly ok, but my C++ paranoia says there is at least a chance of implementation defined behavior.
(*) is case it's not clear (or I messed up) the intended behavior is to set or clear the bits (x1 is the value, x2 is the mask, B is the set/clear op)
If you use unsigned types, all will be OK. The standard mandates that for unsigned target integer types, narrowing is perfectly defined:
4.7 Integral conversions [conv.integral]
...
2 If the destination type is unsigned, the resulting value is the least unsigned integer congruent to the source
integer (modulo 2n where n is the number of bits used to represent the unsigned type).
But if the target type is signed, the result is implementation defined, per the next paragraph (emphasize mine):
3 If the destination type is signed, the value is unchanged if it can be represented in the destination type;
otherwise, the value is implementation-defined.
In common implementations everything will be ok because it is simpler for the compiler to simply do narrowing conversions by only keeping low level bytes for either unsigned or signed types. But the standard only requires that the implementation defines what will happen. An implementation could document that narrowing a value to a signed type when the original value cannot be represented in the target type gives 0, and still be conformant.
By the way, as C++ and C often process conversions the same way, it should be noted that C standard is slightly different because the last case could raise a signal:
6.3.1.3 [Conversions] Signed and unsigned integers
...3 Otherwise, the new type is signed and the value cannot be represented in it; either the
result is implementation-defined or an implementation-defined signal is raised.
Still a confirmation that C and C++ are different languages...

Are there any guarantees on the representation of large enum values?

Suppose I have (on a 32 bit machine)
enum foo {
val1 = 0x7FFFFFFF, // originally '2^31 - 1'
val2,
val3 = 0xFFFFFFFF, // originally '2^32 - 1'
val4,
val5
};
what is the value of val2, val4 and val5? I know I could test it, but is the result standardized?
In C standard:
C11 (n1570), § 6.7.2.2 Enumeration specifiers
Each enumerated type shall be compatible with char, a signed integer type, or an unsigned integer type. The choice of type is implementation-defined, but shall be capable of representing the values of all the members of the enumeration.
If the underlying type used by the compiler is not capable to represent these values, the behavior is undefined.
C11 (n1570), § 4. Conformance
If a ‘‘shall’’ or ‘‘shall not’’ requirement that appears outside of a constraint or runtime-constraint is violated, the behavior is undefined.
From the C++11 standard (§7.2,6, emphasis mine):
For an enumeration whose underlying type is not fixed, the underlying type is an integral type that can represent all the enumerator values defined in the enumeration. If no integral type can represent all the enumerator values, the enumeration is ill-formed. It is implementation-defined which integral type is used as the underlying type except that the underlying type shall not be larger than int unless the value of an enumerator cannot fit in an int or unsigned int.
So the compiler will happily do The Right Thing if there is an integral type bigger than 32bit. If not, the enum is illformed. There will be no wrapping around.
The values will be:
enum foo {
val1 = 0x7FFFFFFF,
val2, // 0x80000000 = 2^31
val3 = 0xFFFFFFFF,
val4, //0x0000000100000000 = 2^32
val5 //0x0000000100000001 = 2^32+1
};
The increasing numbers are well defined as well (§7.2,2):
[...] An enumerator-definition without an initializer gives the enumerator the value obtained by increasing the value of the previous enumerator by one.
C99 / C11
Prelude:
5.2.4.2.1 requires int to be at least 16 bits wide; AFAIK there's no upper bound (long must be longer or equal, though, 6.2.5 /8).
6.5 /5:
If an exceptional condition occurs during the evaluation of an expression (that is, if the result is not mathematically defined or not in the range of representable values for its type), the behavior is undefined.
If your `int` is 32 bits wide (or less)
then the example in the OP is a violation of constraint 6.7.2.2 /2:
The expression that defines the value of an enumeration constant shall be an integer
constant expression that has a value representable as an int.
Furthermore, the enumerators are defined as constant of type int, 6.7.2.2 /3:
The identifiers in an enumerator list are declared as constants that have type int and
may appear wherever such are permitted.
Note, there's a difference between the type of the enumeration and the type of an enumerator / enumeration constant:
enum foo { val0 };
enum foo myVariable; // myVariable has the type of the enumeration
uint_least8_t v = val0*'c'; // if val0 appears in any expression, it has type int
It seems to me this allows narrowing, e.g. reducing the size of the enum type to 8 bits:
enum foo { val1 = 1, val2 = 5 };
enum foo myVariable = val1; // allowed to be 8-bit
But it seems to disallow widening, e.g.
enum foo { val1 = INT_MAX+1 }; // constraint violation AND undefined behaviour
// not sure about the following, we're already in UB-land
enum foo myVariable = val1; // maximum value of an enumerator still is INT_MAX
// therefore myVariable will have sizeof int
Auto-increment of enumerators
Because of 6.7.2.2 /3,
[...] Each subsequent enumerator with no = defines its enumeration constant as the value of the constant expression obtained by adding 1 to the value of the previous enumeration constant. [...]
the example results in UB:
enum foo {
val0 = INT_MAX,
val1 // equivalent to `val1 = INT_MAX+1`
};
Here's the C++ answer: in 7.2/6, it states:
[...] the underlying type is an integral type that can represent all
the enumerator values defined in the enumeration. If no integral type
can represent all the enumerator values, the enumeration is
ill-formed. It is implementation-defined which integral type is used
as the underlying type except that the underlying type shall not be
larger than int unless the value of an enumerator cannot fit in an int
or unsigned int.
So compared to C: no undefined behavior if the compiler can't find a type, and the compiler can't just use its 512-bit extended integer type for your two-value enum.
Which means that in your example, the underlying type will probably be some signed 64-bit type - most compilers always try the signed version of a type first.

What is the correct cast to be used on enums?

If I remember right a C-Style conversion is nothing more than an ordered set of conversions static_cast, dynamic_cast, reinterpret_cast, static_cast...,
consider:
enum NUMBERS
{
NUMBER_ONE,
NUMBER_TWO
};
void Do( NUMBERS a )
{
}
int _tmain(int argc, _TCHAR* argv[])
{
unsigned int a = 1;
Do( a ); //C2664
return 0;
}
a C-Style conversion would do
Do( (NUMBERS)a );
What I would like to know is, what is the correct non C-Style conversion to be made, why?
The correct way would be:
static_cast<NUMBERS>(a)
Because:
8) Integer, floating-point, or enumeration type can be converted to
any enumeration type (the result is unspecified if the value of
expression, converted to the enumeration's underlying type, is not one
of the target enumeration values)
Source: http://en.cppreference.com/w/cpp/language/static_cast
dynamic_cast does runtime checks using RTTI, so it only applies to classes with at least one virtual method.
reinterprest_cast is designed to tell the compiler to treat a specific piece of memory as some different type, without any actual runtime conversions.
static_cast<NUMBERS>(a)
Because the specification for static_cast includes this:
A value of integral or enumeration type can be explicitly converted to
an enumeration type. The value is unchanged if the original value is
within the range of the enumeration values (7.2). Otherwise, the
resulting value is unspecified (and might not be in that range). A
value of floating-point type can also be converted to an enumeration
type. The resulting value is the same as converting the original value
to the underlying type of the enumeration (4.9), and subsequently to
the enumeration type.
static_cast is for generally taking values of one type and getting that value represented as another type. There are about two pages of specification covering all the details, but if you understand that basic idea, then you'll probably know when you want to use static_cast. E.g., if you want to convert an integral value from one integral type to another or if you want to convert a floating point value to an integral value.
dynamic_cast is for working with dynamic types, which is mostly only useful for polymorphic, user-defined types. E.g., convert Base * to Derived * iff the referenced object actually is a Derived.
reinterpret_cast is typically for taking the representation of a value in one type, and getting the value that has the same representation in another type; i.e., type punning (even though a lot of type punning is actually not legal in C++). E.g., if you want to access an integer's storage as an array of char.
const_cast is for adding and removing cv qualifiers (const and volatile) at any level of a type. E.g., int const * volatile **i; const_cast<int volatile ** const *>(i);
static_cast<> is the way to go:
Here's why

What happens if I assign a number with a decimal point to an integer rather than to a float?

A friend of mine asked me this question earlier, but I found myself clutching at straws trying to give him an adequate explanation.
A float or a double will be truncated. So 2.99 will become 2 and -2.99 will become -2.
The pertinent section from the standard (section 4.9)
1 A prvalue of a floating point type can be converted to a prvalue of
an integer type. The conversion truncates; that is, the fractional
part is discarded. The behavior is undefined if the truncated value
cannot be represented in the destination type.
If you assign a value of any numeric type (integer, floating-point) to an object of another numeric type, the value is implicitly converted to the target type. The same thing happens in an initialization or when passing an argument to a function.
The rules for how the conversion is done vary with what kind of types you're using.
If the target type can represent the value exactly:
short s = 42;
int i = s;
double x = 42.0;
int j = x;
there may be a change in representation, but the mathematical value is unchanged.
If a floating-point type is converted to an integer type, and the value can't be represented, it's truncated, as #sashang's answer says -- but if the truncated value can't be represented, the behavior is undefined.
Conversion of an integer (either signed or unsigned) to an unsigned type causes the value to be reduced modulo MAX+1, where MAX is the maximum value of the unsigned type. For example:
unsigned short s = 70000; // sets s to 4464 (70000 - 65536)
// if unsigned short is 16 bits
Conversion of an integer to a signed type, if the value won't fit, is implementation-defined. It typically wraps around in a manner similar to what happens with unsigned types, but the language doesn't guarantee that.

Type Conversion/Casting Confusion in C++

What is Type Conversion and what is Type Casting?
When should I use each of them?
Detail: Sorry if this is an obvious question; I'm new to C++, coming from a ruby background and being used to to_s and to_i and the like.
Conversion is when a value is, um, converted to a different type. The result is a value of the target type, and there are rules for what output value results from what input (of the source type).
For example:
int i = 3;
unsigned int j;
j = i; // the value of "i" is converted to "unsigned int".
The result is the unsigned int value that is equal to i modulo UINT_MAX+1, and this rule is part of the language. So, in this case the value (in English) is still "3", but it's an unsigned int value of 3, which is subtly different from a signed int value of 3.
Note that conversion happened automatically, we just used a signed int value in a position where an unsigned int value is required, and the language defines what that means without us actually saying that we're converting. That's called an "implicit conversion".
"Casting" is an explicit conversion.
For example:
unsigned int k = (unsigned int)i;
long l = long(i);
unsigned int m = static_cast<unsigned int>(i);
are all casts. Specifically, according to 5.4/2 of the standard, k uses a cast-expression, and according to 5.2.3/1, l uses an equivalent thing (except that I've used a different type). m uses a "type conversion operator" (static_cast), but other parts of the standard refer to those as "casts" too.
User-defined types can define "conversion functions" which provide specific rules for converting your type to another type, and single-arg constructors are used in conversions too:
struct Foo {
int a;
Foo(int b) : a(b) {} // single-arg constructor
Foo(int b, int c) : a(b+c) {} // two-arg constructor
operator float () { return float(a); } // conversion function
};
Foo f(3,4); // two-arg constructor
f = static_cast<Foo>(4); // conversion: single-arg constructor is called
float g = f; // conversion: conversion function is called
Classic casting (something like (Bar)foo in C, used in C++ with reinterpret_cast<>) is when the actual memory contents of a variable are assumed to be a variable of a different type. Type conversion (ie. Boost's lexical_cast<> or other user-defined functions which convert types) is when some logic is performed to actually convert a variable from one type to another, like integer to a string, where some code runs to logically form a string out of a given integer.
There is also static and dynamic casting, which are used in inheritance, for instance, to force usage of a parent's member functions on a child's type (dynamic_cast<>), or vice-versa (static_cast<>). Static casting also allows you to perform the typical "implicit" type conversion that occurs when you do something like:
float f = 3.14;
int i = f; //float converted to int by dropping the fraction
which can be rewritten as:
float f = 3.14;
int i = static_cast<int>(f); //same thing
In C++, any expression has a type. when you use an expression of one type (say type S) in a context where a value of another type is required (say type D), the compiler tries to convert the expression from type S to type D. If such an implicit conversion doesn't exist, this results in an error. The word type cast is not standard but is the same as conversion.
E.G.
void f(int x){}
char c;
f(c); //c is converted from char to int.
The conversions are ranked and you can google for promotions vs. conversions for more details.
There are 5 explicit cast operators in C++ static_cast, const_cast, reinterpret_cast and dynamic_cast, and also the C-style cast
Type conversion is when you actually convert a type in another type, for example a string into an integer and vice-versa, a type casting is when the actual content of the memory isn't changed, but the compiler interpret it in a different way.
Type casting indicates you are treating a block of memory differently.
int i = 10;
int* ip = &i;
char* cp = reinterpret_cast<char*>(ip);
if ( *cp == 10 ) // Here, you are treating memory that was declared
{ // as int to be char.
}
Type conversion indicates that you are converting a value from one type to another.
char c = 'A';
int i = c; // This coverts a char to an int.
// Memory used for c is independent of memory
// used for i.