If you add a `short int`

and a `char`

in C++, what is the resulting type? What if you subtract a `long int`

from an `unsigned int`

? The answers actually depend on the compiler and the target architecture (`int`

or `unsigned`

in the first case and `long int`

or `unsigned long int`

in the second). This article lists the rules from the current C++ standard and gives an example of how the type can be resolved at compile time using templates.

### Introduction

Let me first note that I will be referring to the current C++ standard from 1998 (with a minor revision in 2003). This standard is described in The C++ Programming Language by Bjarne Stroustrup. It can also be found online.

In C++, the *integer types* are `short int`

, `int`

, `long int`

and the unsigned versions of these. The integer types together with the boolean type (`bool`

) and the character types (plain/`signed`

/`unsigned`

`char`

and `wchar_t`

) are called *integral types*. The integral types together with the floating-point types (`float`

, `double`

, and `long double`

) are called *arithmetic types*.

### Resolving the Return Type

Consider having the function `fct`

overloaded with one function for each arithmetic type, for example:

`void fct(short v) { std::cout << "short " << v << std::endl; }`

void fct(unsigned short v) { std::cout << "unsigned short " << v << std::endl; }

void fct(int v) { std::cout << "int " << v << std::endl; }

void fct(unsigned v) { std::cout << "unsigned " << v << std::endl; }

...

and so on. If you now have the following little routine:

`void g(short int a, char b) {`

fct(a + b);

}

which instance of `fct`

is called? This is another way of asking the question that started this article.

When a binary operator (`+`

, `-`

, `*`

, `/`

, `%`

) is applied to operands with arithmetic types, the C++ compiler must do the following:

**Integral promotion**. Each operand is, if necessary, promoted to at least an`int`

(to be made more precise below).**Usual arithmetic conversions**. Based on the (possibly promoted) types of the operands, a common type is found. Both operands are converted to this type, which will also be the resulting type.

### Integral Promotions

Integral promotions are defined in Section 4.5, page 4-3, from the online standard and in Section C.6.1, page 833, from The C++ Programming Language. If we ignore enumerations and bit-fields, they can be summed up as follows:

- A
`char`

,`signed char`

,`signed char`

,`short int`

,`unsigned short int`

is converted to`int`

if`int`

can represent all the values of the source type; otherwise, it is converted to an`unsigned int`

. `wchar_t`

is converted to the first of the following types that can represent all the values of`wchar_t`

:`int`

,`unsigned int`

,`long`

,`unsigned long`

.`bool`

is converted to`int`

.

### Usual Arithmetic Conversions

The rules for usual arithmetic conversions can be found in Section 5, page 5-2, from the online standard and in Section C.6.3, page 836, from The C++ Programming Language. Assuming integral promotions have been performed (if needed), the usual arithmetic conversions are in essense the following:

- If one operand is a
`long int`

and the other`unsigned int`

, then if a`long int`

can represent all the values of an`unsigned int`

, the`unsigned int`

shall be converted to a`long int`

; otherwise both operands shall be converted to`unsigned long int`

. - Otherwise, find the highest ranking type among the operands and convert the other operand to this type. The relevant types listed from high to low rank are:
`long double`

,`double`

,`float`

,`unsigned long`

,`long`

,`unsigned`

,`int`

.

### Using Templates To Get The Type

Those were the rules in written form. Imagine now that we have, e.g., the following routine:

`template <typename A, typename B>`

[some-type] add(const A& a, const B& b) { return a + b; }

We would like the types of `add(a, b)`

and `a + b`

to be identical when both `a`

and `b`

are arithmetic types.

First, promotions. By default, a type is not promoted:

`template <typename T>`

struct promote { typedef T type; };

We then use template specializations for the types that need to be promoted. Some of these are easy:

`template <>`

struct promote<signed short> { typedef int type; };

template <>

struct promote<bool> { typedef int type; };

For the rest, we need a sort of if-then-else for choosing a type:

`template <bool C, typename T, typename F>`

struct choose_type { typedef F type; };

template <typename T, typename F>

struct choose_type<true, T, F> { typedef T type; };

So the boolean value of the first argument determines whether to choose the type `T`

(if true) or `F`

(if false). We now have:

`template <>`

struct promote<unsigned short> {

typedef choose_type<sizeof(short) < sizeof(int), int, unsigned>::type type;

};

template <>

struct promote<signed char> {

typedef choose_type<sizeof(char) <= sizeof(int), int, unsigned>::type type;

};

template <>

struct promote<unsigned char> {

typedef choose_type<sizeof(char) < sizeof(int), int, unsigned>::type type;

};

template <>

struct promote<char>

: public promote<choose_type<std::numeric_limits<char>::is_signed,

signed char, unsigned char>::type> {};

This last one for plain `char`

is needed because C++ considers `char`

, `signed char`

, and `unsigned char`

to be three distinct types. The standard does not specify whether `char`

is signed or not. (The `numeric_limits`

template is defined in the `limits`

header.)

Finally, to promote `wchar_t`

:

`template <>`

struct promote<wchar_t> {

typedef choose_type<

std::numeric_limits<wchar_t>::is_signed,

choose_type<sizeof(wchar_t) <= sizeof(int), int, long>::type,

choose_type<sizeof(wchar_t) <= sizeof(int), unsigned, unsigned long>::type

>::type type;

};

We can now turn to the usual arithmetic conversions. First, we promote each type, if necessary:

`template <typename A, typename B>`

struct resolve_uac : public resolve_uac2<typename promote<A>::type,

typename promote<B>::type> {};

This ensures that the type arguments for `resolve_uac2`

are at least `int`

s. We then introduce ranks for those types:

`template <typename T> struct type_rank;`

template <> struct type_rank<int> { static const int rank = 1; };

template <> struct type_rank<unsigned> { static const int rank = 2; };

template <> struct type_rank<long> { static const int rank = 3; };

template <> struct type_rank<unsigned long> { static const int rank = 4; };

template <> struct type_rank<float> { static const int rank = 5; };

template <> struct type_rank<double> { static const int rank = 6; };

template <> struct type_rank<long double> { static const int rank = 7; };

Now we can pick the type with the highest rank:

`template <typename A, typename B>`

struct resolve_uac2 {

typedef typename choose_type<

type_rank<A>::rank >= type_rank<B>::rank, A, B

>::type return_type;

};

Finally we need to deal with the special case where one type is `long int`

and the other is `unsigned int`

:

`template <>`

struct resolve_uac2<long, unsigned> {

typedef choose_type<sizeof(long) == sizeof(unsigned),

unsigned long, long>::type return_type;

};

template <>

struct resolve_uac2<unsigned, long> : public resolve_uac2<long, unsigned> {};

We can now write the `add`

routine from earlier as:

`template <typename A, typename B>`

typename resolve_uac<A, B>::return_type add(const A& a, const B& b)

{ return a + b; }

and the return type will match that of the `+`

operation. Note that the arguments to `add`

have to be arithmetic types (because of the substitution-failure-is-not-an-error principle).

### Remarks

The rules and implementation above should be complete with the exception of enumerations and bit-fields, see the links to the standard for the missing pieces. Note also that the rules for promotions and usual arithmetic conversions will change (slightly) in the upcoming C++0x standard, see the C++0x draft, Section 5, page 84.