Under what circumstances would one use a signed char in C++?

The reason is that you don't know, at least portably, if plain char variables are signed or unsigned. Different implementations have different approaches, a plain char may be signed in one platform and unsigned in another.

If you want to store negative values in a variable of type char, you absolutely must declare it as signed char, because only then you can be sure that every platform will be able to store negative values in there. Yes, you can use [u]int8 type, but this was not always the case (it was only introduced in C++11), and in fact, int8 is most likely an alias for signed char.

Moreover, uint8_t and int8_t are defined to be optional types, meaning you can't always rely on its existence (contrary to signed char). In particular, if a machine has a byte unit with more than 8 bits, it is not very likely that uint8_t and int8_t are defined (although they can; a compiler is always free to provide it and do the appropriate calculations). See this related question: What is int8_t if a machine has > 8 bits per byte?


Is char signed or unsigned?

Actually it is neither, it's implementation defined if a variable of type char can hold negative values. So if you are looking for a portable way to store negative values in a narrow character type explicitly declare it as signed char.

§ 3.9.1 - Fundamental Types - [basic.fundamental]

1 Objects declared as characters (char) shall be large enough to store any member of the implementation's basic character set. If a character from this set is stored in a character object, the integral value of that character object is equal to the value of the single character literal form of that character. It is implementation-defined whether a char object can hold negative values.


I'd like to use the smallest signed integer type available, which one is it?

c++11 introduced several fixed with integer types, but a common misunderstanding is that these types are guaranteed to be available, something which isn't true.

§ 18.4.1 - Header <cstdint> synopsis - [cstdint.syn]

typedefsigned integer typeint8_t; // optional

To preserve space in this post most of the section has been left out, but the optional rationale applies to all {,u}int{8,16,32,64}_t types. An implementation is not required to implement them.


The standard mandates that int_least8_t is available, but as the name implies this type is only guaranteed to have a width equal or larger than 8 bits.

However, the standard guarantees that even though signed char, char, and unsigned char are three distinct types[1] they must occupy the same amount of storage and have the same alignment requirements.

After inspecting the standard further we will also find that sizeof(char) is guaranteed to be 1[2] , which means that this type is guaranteed to occupy the smallest amount of space that a C++ variable can occupy under the given implementation.


Conclusion

Remember that unsigned char and signed char must occupy the same amount of storage as a char?

The smallest signed integer type that is guaranteed to be available is therefore signed char.



[note 1]

§ 3.9.1 - Fundamental Types - [basic.fundamental]

1 Plain char, signed char, and unsigned char are three distinct types, collectively called narrow character types.

A char, a signed char, and an unsigned char occupy the same amount of storage and have the same alignment requirements (3.11); that is, they have the same object representation. For narrow character types, all bits of the object representation participate in the value representation.

[note 2]

§ 5.3.3 - Sizeof - [expr.sizeof]

sizeof(char), sizeof(signed char), and sizeof(unsigned char) are 1.

The result of sizeof applied to any other fundamental type (3.9.1) is implementation-defined.

Tags:

C++

Char