This has to do with the type received by the << operator.
It changed when you used the clause (test ? 3 : '1').
In the first line, the '1' is of type char, which the stream interprets as the character 1, as you see.
However, the result of the test evaluates '1' as an integer, which the stream prints as the numeric value of the ASCII or UTF-8 entry for '1', which is 49 ( 'A' would be 65, 'B' would be 66, '2' would be 50 ).
You could cast, but that's a bit ugly.
You could do: char t = test ? 3 : '1';, and then stream t instead of the expression.
Really, you ought to just avoid using different types that might have unexpected conversions between each other as the two result arguments of the ternary/conditional operator.
Though I'm not sure if I like geekforgeek's explanation.
Basically, what's happening is an implicit conversation, and it looks like it's doing an integral promotion between the two types, leading the result to become an int.
Otherwise, if E2 and E3 have different types, at least one of which is a (possibly cv-qualified) class type, or both are glvalues of the same value category and have the same type except for cv-qualification, then an attempt is made to form an implicit conversion sequence ignoring member access, whether an operand is a bit-field, or whether a conversion function is deleted (since C++14) from each of the operands to the target type determined by the other operand, as described below. An operand (call it X) of type TX can be converted to the target type of the other operand (call it Y) of type TY as follows:
All in all, it's just a thing that C++ will let you do, but maybe you shouldn't do it. Keep the types between the : consistent if you don't want implicit conversions/integral promotions.