Boost logo

Boost Users :

From: dizzy (dizzy_at_[hidden])
Date: 2008-08-16 16:50:05


On Saturday 16 August 2008 20:32:59 Zeljko Vrba wrote:
> Integer types in C and C++ are a mess.

I do not agree. They generally do their job fine (which is provide portable
support to work with native, non checked, platform integer types). For any
other needs you should probably use another tool.

> For example, I have made a library
> where a task is identified by a unique unsigned integer. The extra
> information about the tasks is stored in a std::vector. When a new task is
> created, I use the size() method to get the next id, I assign it to the
> task and then push_back the (pointer to) task structure into the vector.
> Now, the task structure has also an "unsigned int id" field. In 64-bit
> mode,
>
> sizeof(unsigned) == 4, sizeof(std::vector::size_type) == 8
>
> I get a warnings about type truncation, and obviously, I don't like them.

It's perfectly defined to assign values from bigger integrals to smaller
integrals as long as the value can be represented of course. There is nothing
wrong with that. Some compiler warnings can be helpful others not.

> But I like explicit casts and turning off warnings even less.

You can research how to turn off that warning with your compiler only in
specific parts of the code. Obviously the easiest way is explicit conversion.

> No, I don't
> want to tie together size_type and task id type (unsigned int).

Of course not, no reason to tie them.

> One reason
> is "aesthetic", another reason is that I don't want the task id type to be
> larger than necessary (heck, even a 16-bit type would have been enough),
> because the task IDs will be copied verbatim into another
> std::vector<unsigned> for further processing (edge lists of a graph).
> Doubling the size of an integer type shall have bad effects on CPU caches,
> and I don't want to do it.
>
> What to do? Encapsulate into "get_next_id()" function? Have a custom
> size() function/macro that just casts the result of vector::size and
> returns it?

Sure. It is very common in a program to need to assign values from types that
can represent different values as long as your program logic makes sure those
values can be represented at destination, how else can you imagine to do it?

> Another example: an external library defines its interfaces with signed
> integer types, I work with unsigned types (why? to avoid even more warnings
> when comparing task IDs with vector::size() result, as in assert(task->id <
> tasks.size()), which are abundant in my code).

You realise you can fix just that with a helpful function wrapper and you
don't need to change whole interfaces and redesign your code because of a
warning of your compiler being too picky with perfectly fine code right?

> Again, some warnings are
> unavoidable.

The warnings were ment to be helpful. If they are not, turn them off.

> What to do to have "clean" code?

Define "clean" code. If by "clean" code you mean code that does not generate
any warnings but does not use any casts and still has full warnings enabled I
guess you could find cases where the same conversion is performed but no
warning is issued tho that sounds really bad, to write your code depending on
whatever compiler warning algorithm specifics (also notice that even if the
warning is not issued that does not mean the code is safer in any way since
the same possibly dangerous conversion is performed).

> Does anyone know about an integer class that lets the user define the
> number of bits used for storage, lower allowed bound and upper allowed
> bound for the range? Like: template<int BITS, long low, long high> class
> Integer;
>
> BITS would be allowed to assume a value only equal to the one of the
> existing integer types (e.g. 8 for char, 16 for short, etc.), and the class
> would be constrained to hold values in range [low, high] (inclusive).
>
> All operations (+-*/<<>><>) would be compile-time checked to make sense.
> For example, it would be valid to add Integer<32, 0, 8> with Integer<8, 0,
> 8> and store into Integer<8, 0, 16> (or Integer<8, 0, 17>) result, but NOT
> into Integer<8, 0, 8> result. The operations would also be checked at
> run-time to not overflow.

What is nice with C++ is that it offers you the tools to do things. A lot of
people wouldn't need all those checks so they need the low level integrals
that C++ provides. Other people like you do need them so they need these new
integral types. C++ templates, metaprogramming and operator overloading makes
it possible to do it.

> Address-of operator would convert to signed or unsigned underlying integer
> type, depending on the lower bound.

This I don't understand. What has address-of anything to do with integral
values?

> Mixing operations with native integer types would be allowed, provided
> ranges are respected[*].
>
> And, of course, the library should have a "production mode" where the class
> would turn off all checks.
>
> Does anybody know about such a library? Is a unified numeric tower too
> much to ask for, at least in the form of a library? How do you deal with
> these issues?

I think it can be done and it's not very complex, it's a good example of the
power of C++ IMO. However, notice that SUCH a library would not solve your
original descibed problem (it would actually make it from a warning to a
compile time error).

Supose std::vector<T>::size_type is such a checked integral. Since it's
suposed to represent the maximum size possible in memory (actually it's just
the size_type of the Allocator argument but I'm talking about the default one)
then it would be something like integer<32, 0, 4294967295>. Then, your thread
ID is something like integer<16, 0, 65535>. Then the compile time checks you
talked about woudl not allow you to assign values from what vector.size()
returns to your thread IDs. So what you actually do is get to the same old
solution we have now, you convert values from a type being able to represent
more to a type with less values after you have made sure the values can be
stored. So how exactly does your library solve anything then?

>
> [*] If there's no such library in existence, I might be willing to think
> about a set of "sane" axioms and checks for automatic conversions. And I
> have "Hacker's Delight" at home, so I can peek into it and get formulas for
> deriving bounds of arithmetic operations.
>
> And yes, is this even easily implementable within C++ template framework;
> one has to deal with integer overflow.. What happens in
>
> template<long A, long B> class blah { const long T = A+B; };
>
> when A+B overflows? Compiler error? Undefined result?
> Did C++0x do
> anything to clean up the integer types mess?

If it does overflow what would you have liked the standard to say about it?

> This is a proposal for extension to the C language:
> http://www.cert.org/archive/pdf/07tn027.pdf
>
> Anything similar available for C++?
>
> Or should I just listen to the devil on my shoulder and turn off the
> appropriate warnings?

Obviously so if you are doing correct code. By your reasoning one should find
a solution for the common if(a = b) warning other than:
- turn off the warning
- or wrap with another set of paranthesis if((a = b)) (the equivalent of the
explicit conversion in your case)

There is a lot of possible dangerous code that can be done in C or C++ and
some compilers tell you about it but that do not make it wrong code. Warnings
were put into compilers to catch on common pitfalls but they generally do not
mean the code is wrong.

-- 
Dizzy

Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net