7

Suppose I have an integer type T (signed or unsigned). I want to refer (at compile time) to the smallest integer type (signed or unsigned) which can hold, say, std::numeric_limits<T>::max() plus 1 (in the non-overflowing sense, I mean).

What's a nice generic way of doing that?

einpoklum
  • 118,144
  • 57
  • 340
  • 684

4 Answers4

3

For unsigned types, this does the trick:

template <typename T>
constexpr unsigned size_in_bits() { return  sizeof(T) * CHAR_BIT; }

template <typename T>
using least_larger_uint_t = 
    typename boost::uint_t<size_in_bits<T>() + 1>::least;

And if we want something to work for any integer type:

template <typename T, int NumBits>
using boost_integer_type = 
    typename std::conditional<
        std::is_unsigned<T>::value,
        boost::uint_t<NumBits>, 
        boost::int_t<NumBits>
    >::type;

template <typename T>
constexpr unsigned size_in_bits() { return sizeof(T) * CHAR_BIT; }

template <typename T>
using least_larger_integral_t = 
    typename boost_integer_type<T, size_in_bits<T>() + 1>::least;

See the documentation for Boost.Integer for details on int_t<N> and uint_t<N>.

einpoklum
  • 118,144
  • 57
  • 340
  • 684
2

You might specify integers by required size and select accordingly:

#include <cstdint>

namespace Detail {
    template <std::size_t> struct integer_of_size_undefined {};
    template <> struct integer_of_size_undefined<sizeof(int8_t)> {
        typedef int8_t type;
    };
    template <> struct integer_of_size_undefined<sizeof(int16_t)> {
        typedef int16_t type;
    };
    template <> struct integer_of_size_undefined<sizeof(int32_t)> {
        typedef int32_t type;
    };
    template <> struct integer_of_size_undefined<sizeof(int64_t)> {
        typedef int64_t type;
    };

    template <std::size_t> struct unsigned_integer_of_size_undefined {};
    template <> struct unsigned_integer_of_size_undefined<sizeof(int8_t)> {
        typedef uint8_t type;
    };
    template <> struct unsigned_integer_of_size_undefined<sizeof(int16_t)> {
        typedef uint16_t type;
    };
    template <> struct unsigned_integer_of_size_undefined<sizeof(int32_t)> {
        typedef uint32_t type;
    };
    template <> struct unsigned_integer_of_size_undefined<sizeof(int64_t)> {
        typedef uint64_t type;
    };
}

template <std::size_t N>
struct integer_of_size {
    typedef typename Detail::integer_of_size_undefined<N>::type type;
};

template <std::size_t N>
struct unsigned_integer_of_size {
    typedef typename Detail::unsigned_integer_of_size_undefined<N>::type type;
};


#include <type_traits>

template <typename T>
struct next_integer {
    typedef typename std::conditional<std::is_signed<T>::value,
        typename std::make_unsigned<T>::type,
        typename integer_of_size<2*sizeof(T)>::type>::type
        type;
};

int main ()
{
    static_assert(std::is_same<next_integer<std::int16_t>::type, uint16_t>::value,
        "Should be a unsigned 16 bit");
    static_assert(std::is_same<next_integer<std::uint16_t>::type, int32_t>::value,
        "Should be a signed 32 bit");

    return 0;
}
  • Does the standard guarantee there can be no integral type of size 3, or 6? – einpoklum Jul 27 '16 at 13:40
  • ... in that case, I don't want _my_ code to be the one making that assumption, I would rather rely on what's already expressed in the standard library (or at most Boost, as in my answer) for that knowledge... – einpoklum Jul 27 '16 at 13:54
0

You can do something like this, using the types from cstdint:

template <typename T>
struct next_type {
    typedef error_type type;
};

template <>
struct next_type<std::int32_t> {
    typedef std::uint32_t type;
}

template <>
struct next_type<std::uint32_t> {
    typedef std::int64_t type;
}

// Add more...

Adding the specializations for each type. Then use it:

typename next_type<T>::type

So if you are using the maximum possible type already, you are getting the error_type, which you must somehow define what it is yourself, otherwise it takes the specialization.

It would perhaps be better to use the _least types from cstdint, like std::int_least32_t goes to std::uint_least32_t.

Franko Leon Tokalić
  • 1,457
  • 3
  • 22
  • 28
0

If you want something to work well on every integer type, you can embed this in a trait built in several steps. We begin by mapping integral types with their bit size:

#define CREATE_SIGNED_META_OBJ(x) template <>\
    struct signed_integer_type<x> {\
        typedef int##x##_t type;\
    };

#define CREATE_UNSIGNED_META_OBJ(x) template <>\
    struct unsigned_integer_type<x> {\
        typedef uint##x##_t type;\
    };

template <std::size_t length>
struct signed_integer_type;

template <std::size_t len>
struct unsigned_integer_type;

CREATE_SIGNED_META_OBJ(8)
CREATE_SIGNED_META_OBJ(16)
CREATE_SIGNED_META_OBJ(32)
CREATE_SIGNED_META_OBJ(64)

CREATE_UNSIGNED_META_OBJ(8)
CREATE_UNSIGNED_META_OBJ(16)
CREATE_UNSIGNED_META_OBJ(32)
CREATE_UNSIGNED_META_OBJ(64)

Then, the trait in itself can be built. We want to apply our dichotomy on the value of the assertion std::numeric_limits<Int>::min() == 0...

template <typename Int, bool>
struct get_smallest_for_max_plus_one;

template <typename Int>
struct get_smallest_for_max_plus_one<Int, true> {
    typedef typename signed_integer_type<2*sizeof(Int)*8>::type type;
};

template <typename Int>
struct get_smallest_for_max_plus_one<Int, false> {
    typedef typename unsigned_integer_type<sizeof(Int)*8>::type type;
};

template <typename Int>
using get_fittest_int_type = get_smallest_for_max_plus_one<Int, std::numeric_limits<Int>::min() == 0>;

Now we can use directly get_fittest_int_type with any integral integer... Running examples can be found on Coliru.

For consistency's sake though, I guess you want to keep the signed or unsigned property... If this is the case, you could just replace the specializations of get_smallest_for_max_plus_one with the following:

// Unsigned type, we want to get the smallest unsigned type that can hold max + 1
template <typename Int>
struct get_smallest_for_max_plus_one<Int, true> {
    typedef typename unsigned_integer_type<2*sizeof(Int)*8>::type type;
};

// Signed type, we want the smallest signed type that can hold max + 1
template <typename Int>
struct get_smallest_for_max_plus_one<Int, false> {
    typedef typename signed_integer_type<2*sizeof(Int)*8>::type type;
};
Rerito
  • 5,886
  • 21
  • 47
  • ... but does the C++ standard actually guarantee there can be no integer types of size 3 or 6? – einpoklum Jul 27 '16 at 13:41
  • @einpoklum Your doubts seem legit to me, I didn't check this fact. I just provided a minimal example with common integer types. Your solution based on boost seems more versatile in that regard! – Rerito Jul 27 '16 at 13:43