Why is std::common_type (ternary operator) of unsigned int and int = unsigned int, and not long?

120 views
Skip to first unread message

h...@nnesm.de

unread,
Mar 13, 2013, 6:46:20 AM3/13/13
to std-dis...@isocpp.org
Hi everybody,

I recently discovered some unexpected behavior of std::common_type. Let me directly start with a code example, so that you can try it out for yourself:

#include <iostream>
#include <typeinfo>
#include <type_traits>


int main(int argc, const char* argv[])
{

    std::cout << typeid(std::common_type<char, unsigned char>::type).name() << std::endl;
    // I would expect "int", and the result is "int", ok so far.

    std::cout << typeid(std::common_type<int, unsigned int>::type).name() << std::endl;
    // I would expect "long", but the result is "unsigned int"

    std::cout << typeid(std::common_type<long, unsigned long>::type).name() << std::endl;
    // I would expect "long long", but the result is "unsigned long"


    // I know that common_type has the same behavior as the ternary operator.
    // Basically this is how it's implemented, I guess (At least from what I saw in boost::type_traits)
    auto var_auto = true ? var_i : var_ui;
    std::cout << typeid(var_auto).name() << std::endl;   // unsigned int
    std::cout << var_auto << std::endl;                  // 4294967173

    return 0;
}

So why is it that up to integer, the integral promotion works as expected (common_type of char and unsigned char is int, which comprises all valid values...), but as soon as it gets bigger than int, the signed part is neglected?
Isn't this some kind of unexpected behavior?
Or is it intended and I just don't get why?

- Hannes

Daniel Krügler

unread,
Mar 13, 2013, 8:59:23 AM3/13/13
to std-dis...@isocpp.org
2013/3/13 <h...@nnesm.de>:
> Hi everybody,
>
> I recently discovered some unexpected behavior of std::common_type. Let me
> directly start with a code example, so that you can try it out for yourself:
>
> #include <iostream>
> #include <typeinfo>
> #include <type_traits>
>
> int main(int argc, const char* argv[])
> {
>
> std::cout << typeid(std::common_type<char, unsigned char>::type).name()
> << std::endl;
> // I would expect "int", and the result is "int", ok so far.
>
> std::cout << typeid(std::common_type<int, unsigned int>::type).name() <<
> std::endl;
> // I would expect "long", but the result is "unsigned int"
>
> std::cout << typeid(std::common_type<long, unsigned long>::type).name()
> << std::endl;
> // I would expect "long long", but the result is "unsigned long"

This looks correct to me as of the current wording (see below)

> // I know that common_type has the same behavior as the ternary
> operator.
> // Basically this is how it's implemented, I guess (At least from what I
> saw in boost::type_traits)
> auto var_auto = true ? var_i : var_ui;
> std::cout << typeid(var_auto).name() << std::endl; // unsigned int
> std::cout << var_auto << std::endl; // 4294967173
>
> return 0;
> }
>
> So why is it that up to integer, the integral promotion works as expected
> (common_type of char and unsigned char is int, which comprises all valid
> values...), but as soon as it gets bigger than int, the signed part is
> neglected?
> Isn't this some kind of unexpected behavior?
> Or is it intended and I just don't get why?

This is how the core language works when performing the so-called
"usual arithmetic conversions" described in 5 p10. The relevant case
is:

"Otherwise, if the operand that has unsigned integer type has rank
greater than or equal to the
rank of the type of the other operand, the operand with signed integer
type shall be converted to
the type of the operand with unsigned integer type."

The current behaviour is a known problem. There exists a proposal that
attempts to remove at least some of these surprises:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2012/n3387.html

- Daniel
Reply all
Reply to author
Forward
0 new messages