You're in
Iker Hurtado's pro blog
Developer | Entrepreneur | Investor
Software engineer (entrepreneur and investor at times). These days doing performant frontend and graphics on the web platform at Barcelona Supercomputing Center

Relearning C++: Notes on variables, basic types, type conversion and casting

5 Mar 2015   |   iker hurtado  
Share on Twitter Share on Google+ Share on Facebook
In this post I write down some notes as from my study of C++ basics: variables, basic types, type conversion and casting

Variables and basic types

A variable is a named location in memory that can store a value of a particular type. The use of names (or identifiers) -attached to memory addresses- instead of real numerical addresses facilitate our value-store management.

A type (int, double, char) is associated with that value-store item (variable) in order to make easier the interpretation of its data. The type determines the size (and the value range), layout of the data and the set of operations that can be applied.

Understanding their arrangement in memory

Each address location usually hold 8-bit (1-byte) of data. For example, a char value occupies 1 memory location and a 4-byte int value 4 memory locations.

A 32-bit system typically uses 32-bit addresses. To store a 32-bit address, 4 memory locations are required.

This scheme is very explanatory:

Fundamental Types

Fundamental data types are basic types implemented directly by the language. They represent the basic storage units supported natively by most systems.

None of the fundamental types (excepting char) has a standard size specified (but yes a minimum size). This does not mean that these types have an undetermined size, but that there is no standard size across all compilers and machines; each compiler implementation may specify the sizes that fit the best the architecture where the program is going to run.

In order to provides information about the properties and limits of arithmetic types in the specific platform for which the library compiles the climits and cfloat headers were ported over from C's limit.h and float.h. More recently C++ added a new header called limits that gather all this arithmetic types data.

Some tips for choosing the arithmetic types

The number of arithmetic types in C++ is too big for compatibility and low level issues. Some few rules of thumb for deciding which type to use:

• Use an unsigned type if the possible variable values cannot be negative.

• Use int for integer arithmetic. short is usually too small and long often has the same size as int. If the possible values are larger than the minimum guaranteed size of int, then use the long long type.

• Do not use plain char or bool in arithmetic expressions. The use of char in arithmetic is tricky because it is signed on some machines and unsigned on others. If it's necessary a tiny integer, explicitly specify either signed char or unsigned char.

• Use double for floating-point computations; float usually does not have enough precision and the difference of cost is negligible.

Don’t mix signed and unsigned types in expressions
Expressions that mix signed and unsigned values can yield surprising results when the signed value is negative. Signed values are automatically converted to unsigned in these conditions.

Initialization of variables

In C++, there are three ways to initialize variables. They are all equivalent and are reminiscent of the evolution of the language over the years:

int x = 0;  // c-like initialization: it is inherited from the C language
int x (0);  // constructor initialization (introduced by the C++ language)
int x {0};  // uniform initialization (introduced by the C++11 revision)

Type deduction

auto and decltype are powerful features recently added to the language in order to be used when the type cannot be obtained by other means or when using it improves code readability.

The auto Type Specifier

Sometimes, when we want to store the value of an expression in a variable we cannot know the resulting value type. Under the new standard, we can let the compiler figure out the type for us by using the auto type specifier.

A variable that uses the auto type specifier must have an initializer:

// the type of item is deduced from the type of the result of adding val1 and val2
auto item = v1 + v2; 

The decltype Type Specifier

At other times, we want to define a variable with a type that the compiler deduces from an expression but do not want to use that expression to initialize the variable. For such cases, there is a second type specifier, decltype, which returns the type of its operand. The compiler analyzes the expression but does not evaluate the expression:

decltype(f()) v = x; // v has the type f returns

The typedef Statement Typing "unsigned int" many time can get annoying. The typedef statement can be used to create a new name for an existing type. For example, you can create a new type called "uint" for "unsigned int" as follow. You should place the typedef immediately after #include. Use typedef with care because it makes the program hard to read and understand. typedef unsigned int uint; Many C/C++ compilers define a type called size_t, which is a typedef of unsigned int. typedef unsigned int size_t;

Type conversion and casting

Implicit (automatic) type conversion

In C/C++ some types are related to each other when there is a conversion between them. In that case, it's possible to use an object or value of one type where an operand of the related type is expected.

These conversions are carried out by the compiler automatically without programmer intervention -and sometimes without programmer knowledge. For that reason, they are referred to as implicit conversions.

Standard conversions

Standard conversions affect fundamental data types, and allow the conversions between numerical types, to or from bool, and some pointer conversions.

Converting to int from some smaller integer type, or to double from float is known as promotion, and it guarantees the exact same value in the destination type. Other conversions between arithmetic types may not always represent the same value exactly.

The types bool, char, signed char, unsigned char, short, and unsigned short are promoted to int if all possible values of that type fit in this type. Otherwise, the value is promoted to unsigned int.

In expressions that mix floating-point and integral values, the integral value is converted to an appropriate floating-point type.

I extract this snippet form cplusplus.com article (although I don't understand it completely because it seems important):

As for non-fundamental types, arrays and functions implicitly convert to pointers, and pointers in general allow the following conversions:

- Null pointers can be converted to pointers of any type.

- Pointers to any type can be converted to void pointers.

- Pointer upcast: pointers to a derived class can be converted to a pointer of an accessible and unambiguous base class, without modifying its const or volatile qualification.

Where the compiler converts

Some circumstances where compiler automatically converts operands are:

• In most expressions, numerical values are converted as seen above.

• In conditions, nonbool expressions are converted to bool.

• In initializations, the initializer is converted to the type of the variable; in assignments, the right-hand operand is converted to the type of the left-hand.

• In arithmetic and relational expressions with operands of mixed types, the types are converted to a common type.

• During function calls.

Conversions on Classes

As for the behavior of programmer defined classes, implicit conversions can be controlled by means of three member functions:

- Single-argument constructors: allow implicit conversion from a particular type to initialize an object.

B (const A& x) {}   // conversion from A (constructor) 
B bar = a;   // calls constructor

- Assignment operator: allow implicit conversion from a particular type on assignments.

B& operator= (const A& x) {return *this;}   // conversion from A (assignment)
b = a;   // calls assignment

- Type-cast operator: allow implicit conversion to a particular type.

operator A() {return A();}   // conversion to A (type-cast operator)
a = b;   // calls type-cast operator

In function calls implicit conversions can happen for each argument. This may be somewhat tricky for classes, because it is not always wanted.

Fortunately, it can be prevented with the explicit keyword. Constructors and type-cast member functions can be specified as explicit. It's worthy note that constructors marked with explicit cannot be called with the assignment-like syntax.

Explicit Type-Casting

It's possible explicitly to perform type-casting via the unary type-casting operator. The regular cast is right for fundamental types but it can be dangerous for the rest -like written in cplusplus.com article:

The functionality of these generic forms of type-casting is enough for most needs with fundamental data types. However, these operators can be applied indiscriminately on classes and pointers to classes, which can lead to code that -while being syntactically correct- can cause runtime errors.

In order to control the conversions between classes C++ introduced four new type casting operators: const_cast(value), static_cast(value), dynamic_cast(value), reinterpret_cast(value). Although the old styles is still acceptable in C++, new styles are preferable.

I briefly explain some of them:

static_cast is used for force implicit conversion. It throws a type-cast error if the conversion fails. Is appropriate to use static_cast to convert values of various fundamental types.

dynamic_cast can be used to verify the type of an object at runtime, before performing the type conversion. It is primarily used to perform "safe downcasting".

const_cast can be used to change temporally the const behavior. This is useful if you have a variable which is constant most of the time, but need to be changed in some circumstances.