Most of this is required behavior (and most of what isn't still borders on being required).
To be specific, the C++ standard says that iostreams are associated with C-style input and output streams, so cout
is associated with stdout
(§[narrow.stream.objects]/3):
The object cout
controls output to a stream buffer associated with the object stdout
, declared in <cstdio>
.
The C standard, in turn, defines narrow-character output as being as-if written via fputc
(§7.19.3/12):
The byte output functions write characters to the stream as if by successive calls to the fputc
function.
fputc
requires (§7.19.7.3/2):
The fputc
function writes the character specified by c
(converted to an unsigned char
) to the output stream pointed to by stream
, [...]
So, yes, the conversion to unsigned char
is exactly what the standards require. The C standard requires that conversion from signed to unsigned (of any integer type, including char
) happen in the following fashion (§6.3.1.3/2):
Otherwise, if the new type is unsigned, the value is converted by repeatedly adding or subtracting one more than the maximum value that can be represented in the new type until the value is in the range of the new type.
So yes, it is converted to unsigned by adding 256 (assuming unsigned char
can represent values from 0 to 255, as is typical).
So that leaves us one part that the standard sort of attempts to require, without going quite all the way--the transformation that widen
has to do (§[locale.ctype.virtuals]/10):
Applies the simplest reasonable transformation from a char value or sequence of char values to the corresponding charT value or values.
Since it's a little difficult to decide exactly what's "reasonable", this could carry out some more or less arbitrary mapping on your character. In fact, it's apparently mapping input to output without modification (at least for the particular character you're writing), but it's true that other transformations could fall within "reasonable", and it would ultimately be difficult to draw a hard line saying that any particular transformation was not "reasonable".
The other part that's not really required by the C++ (or any other) standard is how something else will interpret that output. All the language standards can mandate is what gets written to the stream. The part with something else opening that stream and interpreting its content as "extended ASCII" (probably one of the ISO 8859 variants) is clearly outside the control of the language (or much of anything else in your program, of course).