Are int8_t and uint8_t intended to be char types?

From § 18.4.1 [cstdint.syn] of the C++0x FDIS (N3290), int8_t is an optional typedef that is specified as follows:

namespace std {
  typedef signed integer type int8_t;  // optional
  //...
} // namespace std

§ 3.9.1 [basic.fundamental] states:

There are five standard signed integer types: “signed char”, “short int”, “int”, “long int”, and “long long int”. In this list, each type provides at least as much storage as those preceding it in the list. There may also be implementation-defined extended signed integer types. The standard and extended signed integer types are collectively called signed integer types.

...

Types bool, char, char16_t, char32_t, wchar_t, and the signed and unsigned integer types are collectively called integral types. A synonym for integral type is integer type.

§ 3.9.1 also states:

In any particular implementation, a plain char object can take on either the same values as a signed char or an unsigned char; which one is implementation-defined.

It is tempting to conclude that int8_t may be a typedef of char provided char objects take on signed values; however, this is not the case as char is not among the list of signed integer types (standard and possibly extended signed integer types). See also Stephan T. Lavavej's comments on std::make_unsigned and std::make_signed.

Therefore, either int8_t is a typedef of signed char or it is an extended signed integer type whose objects occupy exactly 8 bits of storage.

To answer your question, though, you should not make assumptions. Because functions of both forms x.operator<<(y) and operator<<(x,y) have been defined, § 13.5.3 [over.binary] says that we refer to § 13.3.1.2 [over.match.oper] to determine the interpretation of std::cout << i. § 13.3.1.2 in turn says that the implementation selects from the set of candidate functions according to § 13.3.2 and § 13.3.3. We then look to § 13.3.3.2 [over.ics.rank] to determine that:

  • The template<class traits> basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>&, signed char) template would be called if int8_t is an Exact Match for signed char (i.e. a typedef of signed char).
  • Otherwise, the int8_t would be promoted to int and the basic_ostream<charT,traits>& operator<<(int n) member function would be called.

In the case of std::cout << u for u a uint8_t object:

  • The template<class traits> basic_ostream<char,traits>& operator<<(basic_ostream<char,traits>&, unsigned char) template would be called if uint8_t is an Exact Match for unsigned char.
  • Otherwise, since int can represent all uint8_t values, the uint8_t would be promoted to int and the basic_ostream<charT,traits>& operator<<(int n) member function would be called.

If you always want to print a character, the safest and most clear option is:

std::cout << static_cast<signed char>(i);

And if you always want to print a number:

std::cout << static_cast<int>(i);

int8_t is exactly 8 bits wide (if it exists).

The only predefined integer types that can be 8 bits are char, unsigned char, and signed char. Both short and unsigned short are required to be at least 16 bits.

So int8_t must be a typedef for either signed char or plain char (the latter if plain char is signed).

If you want to print an int8_t value as an integer rather than as a character, you can explicitly convert it to int.

In principle, a C++ compiler could define an 8-bit extended integer type (perhaps called something like __int8), and make int8_t a typedef for it. The only reason I can think of to do so would be to avoid making int8_t a character type. I don't know of any C++ compilers that have actually done this.

Both int8_t and extended integer types were introduced in C99. For C, there's no particular reason to define an 8-bit extended integer type when the char types are available.

UPDATE:

I'm not entirely comfortable with this conclusion. int8_t and uint8_t were introduced in C99. In C, it doesn't particularly matter whether they're character types or not; there are no operations for which the distinction makes a real difference. (Even putc(), the lowest-level character output routine in standard C, takes the character to be printed as an int argument). int8_t, and uint8_t, if they're defined, will almost certainly be defined as character types -- but character types are just small integer types.

C++ provides specific overloaded versions of operator<< for char, signed char, and unsigned char, so that std::cout << 'A' and std::cout << 65 produce very different output. Later, C++ adopted int8_t and uint8_t, but in such a way that, as in C, they're almost certainly character types. For most operations, this doesn't matter any more than it does in C, but for std::cout << ... it does make a difference, since this:

uint8_t x = 65;
std::cout << x;

will probably print the letter A rather than the number 65.

If you want consistent behavior, add a cast:

uint8_t x = 65;
std::cout << int(x); // or static_cast<int>(x) if you prefer

I think the root of the problem is that there's something missing from the language: very narrow integer types that are not character types.

As for the intent, I could speculate that the committee members either didn't think about the issue, or decided it wasn't worth addressing. One could argue (and I would) that the benefits of adding the [u]int*_t types to the standard outweighs the inconvenience of their rather odd behavior with std::cout << ....