Writing std::min/std::max for ints of heterogenous widths
Asked Answered
S

1

6

My C++ program uses unsigned ints of different widths to express constraints on what data can be represented. For example, I have a file whose size is a uint64_t, and I wish to read it in chunks with a buffer whose size is a size_t. A chunk is the smaller of the buffer size and the (remaining) file size:

uint64_t file_size = ...;
size_t buffer_size = ...;
size_t chunk_size = std::min(buffer_size, file_size);

but this fails because std::min requires that both parameters have the same type, so I must cast up and then back down:

size_t chunk_size = \
    static_cast<size_t>(std::min(static_cast<uint64_t>)buffer_size, \
                                 file_size));

This casting ought to be unnecessary, because it is obvious that min(size_t, uint64_t) will always fit in a size_t.

How can I write a generic min function that takes two (possibly different) unsigned types, and whose return type is the smaller of the two types?

Servility answered 11/7, 2015 at 19:52 Comment(2)
I have an implementation of this somewhere, but it was on gitorious I think ... Also it was GPL and I don't feel comfortable relicensing it to meet SO's requirements.Tidbit
Dug it up and made a gist for those okay with GPL3+: gist.github.com/o11c/0d5c10684d8fe713d7f3Tidbit
C
5

If you want to explicitly cast to the smaller one, you should just tell the compiler that:

std::min<smaller_type>(buffer_size, file_size)

That's pretty explicit and everybody will understand what you're doing. I would just stick with that. You're providing the type you want at the point of call. That is more readable than hiding it behind a function template somewhere every time.

Although, given the potential overflow issues, you could do:

template <typename U, typename V>
using smaller_type = std::conditional_t<sizeof(U) < sizeof(V), U, V>;

template <typename U, typename V>
using larger_type = std::conditional_t<sizeof(V) < sizeof(U), U, V>;

template <typename U, typename V>
smaller_type<U, V> my_min(const U& u, const V& v) {
    return std::min<larger_type<U, V>>(u, v);
}
Crucifixion answered 11/7, 2015 at 19:54 Comment(5)
This elegantly solves the need for casting, at the cost of some correctness. std::min<uint32_t>(1, 1LLU<<32) is 0 when it ought to be 1.Servility
@Servility Updated with a function template solution. Of course you'll still have issues with signed-ness.Crucifixion
Cool, I didn't know about std::conditional. That's very handy! If you fix your code to make it compile (for future searchers) I'll accept your answer.Servility
Not fixed, but I'll accept. If you get a chance please fix it (conditional_t -> conditional, missing typename and ::type)Servility
@Servility That's not an error. C++14 introduced aliases for the type traits (see conditional). If you don't have a C++14 compiler, I'd suggest just adding in the alises yourself. The typename trait<X>::type syntax is a thing of the past.Crucifixion

© 2022 - 2024 — McMap. All rights reserved.