20

common_type<long, unsigned long>::type is unsigned long because concerning the operands after integral promotion the standard says...

[...] 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

Not to call the integral promotion system buggy, but it seems like if there is a bigger signed integer type which can can represent the range of both signed and unsigned operands it should be used.

I know some platforms might have long == long long, in which case the above rule can take effect. But if there is a larger signed integral type available, shouldn't it be used?

David
  • 27,652
  • 18
  • 89
  • 138
  • 4
    I don't believe there is any guarantee that `long long` will encompass the entire range of `unsigned long`. If it's like the rest of the size specifications, the only requirement is that it is represented with at least as many bits as `long`. Type promotion should behave uniformly regardless of platform, so there is some predictability w/ regard to overload resolution. – jpm Mar 04 '13 at 21:36
  • 2
    `std::common_type` matches the rules for determining the return type of the ternary operator. Viewed in that light, it seems obviously wrong that the ternary operator would return a type larger than either of its two branches. – Lily Ballard Mar 04 '13 at 21:37
  • @KevinBallard I don't actually think that seems obviously wrong. Returning a type larger than either of its branches when the branches return different signed-ness seems like the only thing to do that's guaranteed to be bug free. – David Mar 04 '13 at 21:52
  • 3
    @KevinBallard And you expect `(b?(unsigned char)255:(signed char)-1)` to do what? – Yakk - Adam Nevraumont Mar 04 '13 at 21:52
  • @Yakk: Ok I guess what I said doesn't hold true for numeric types smaller than `int`. – Lily Ballard Mar 04 '13 at 21:55
  • Maybe the correct thing should be to promote to the smallest signed integer which can represent the range of either of the operands. If no such integer exists it's a compilation error and the programmer should have to do an explicit cast. – David Mar 04 '13 at 22:08
  • @Yakk: Sorry in advance, I'm still new to this.. I would like to understand your question to Kevin: what would be the returned type in your example? Wouldn't it be `unsigned char` like Dave explains with `long`s? – Jonathan H Mar 04 '13 at 22:22
  • 1
    @Sh3ljohn Integral promotion rules are... odd. Types smaller than `int` promote to `int`. The rule I posted applies after intergral promotion. – David Mar 04 '13 at 22:23
  • A related question [std::common_type](http://stackoverflow.com/questions/6503773/stdcommon-type). – Bo Persson Mar 04 '13 at 22:44
  • This is probably part of the reason why [Java doesn't have unsigned integers](http://stackoverflow.com/questions/430346/why-doesnt-java-support-unsigned-ints). – Mark Ransom Mar 13 '13 at 16:37
  • @MarkRansom Didn't know that about Java, and they do cite complex C unsigned int rules as a reason it's not there. But it's dumb that the rules are error prone, they don't have to be. If signed+unsigned always promoted to a larger signed type that could encompass the range of both inputs, and if no larger type was available it was an error, this would all be entirely bug free and no one would care about this needless complexity. – David Mar 13 '13 at 16:46

1 Answers1

7

first of all, std::common_type (and of course boost::type_traits::common_type) use the ternary operator to retrieve the type result. In this case the relevant quote comes from the CppReference, 6b)

E2 and E3 have arithmetic or enumeration type: usual arithmetic conversions are applied to bring them to common type, that type is the result.

With this information we can find the rules for the usual arithmetic conversions in the c++ standard, 5p10, page 88.

— 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.

So basically the answer to your question is: ...because the standard says so.

But you are not the only one finding this behavior unexpected. Here's a quick runnable example to try:

#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 "short", and the result is "int", ok so far.

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

    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"


    // So this usual arithmetic conversion can lead to unexpected behavior:
    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;
}

But that the current behavior is a problem is known, and a proposal exists to remove some of the surprises.

-Hannes

Hannes M
  • 741
  • 7
  • 20
  • So then what is your question? I answered "Why isn't common_type::type = long long?" Your Example does not add more information than my example, correct me if I'm wrong. But of course 1 < *MaxULong*-1... – Hannes M Mar 13 '13 at 19:42
  • It was clear in the question that the standard indeed says that's the way it is, I didn't need an answer that reaffirms it's this way *because the standard says so*. But like I said, I'm glad you had your links at the bottom, they are relevant and a partial answer (in that it's a known 'problem', but doesn't show why it was this way to begin with) – David Mar 13 '13 at 21:10