tglas
tglas

Reputation: 1010

Is there a complete and portable list of all different integer types in C++?

I want to implement a function handling all simple C++ types (also STL containers and more types, but they don't give me headakes), with a generic function handling all other types, as follows:

template <typename T>
void dostuff(T& arg)
{ cout << "generic version" << endl; }
void dostuff(int& arg)
{ cout << "something useful" << endl; }
void dostuff(unsigned int& arg)
{ cout << "something slightly different" << endl; }
void dostuff(short& arg)
{ cout << "something slightly different again" << endl; }
// ...and so on for all integer types...

Here I restrict myself to integer types since they are causing all the trouble. Of course, the compiler complains if I implement the function for the same type twice. This happens when mixing things like unsigned int and std::size_t; it works on my system but does not compile on a 32 bit platform. I understand that problem, but I don't know how to achieve a portable solution.

I thought that the most systematic approach would be to use the std::int8_t (std::uint8_t, 16, 32, 64) family of types, since they won't overlap and are designed to cover the available range. It turns out that this is not enough, as demonstrated by the following code:

#include <type_traits>
#include <cstdint>
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
    cout << "SIZE:" << endl;
    cout << "sizeof(unsigned long)      = " << sizeof(unsigned long) << endl;
    cout << "sizeof(unsigned long long) = " << sizeof(unsigned long long) << endl;
    cout << "sizeof(std::size_t)        = " << sizeof(std::size_t) << endl;
    cout << "sizeof(std::uint64_t)      = " << sizeof(std::uint64_t) << endl;
    cout << endl;

    cout << "SIGNED?" << std::boolalpha << endl;
    cout << std::is_signed<unsigned long>::value << endl;
    cout << std::is_signed<unsigned long long>::value << endl;
    cout << std::is_signed<std::size_t>::value << endl;
    cout << std::is_signed<std::uint64_t>::value << endl;
    cout << endl;

    cout << "SAME?" << endl;
    cout << std::is_same<unsigned long, unsigned long long>::value << endl;
    cout << std::is_same<unsigned long, std::size_t>::value << endl;
    cout << std::is_same<unsigned long, std::uint64_t>::value << endl;
    cout << std::is_same<unsigned long long, std::size_t>::value << endl;
    cout << std::is_same<unsigned long long, std::uint64_t>::value << endl;
    cout << std::is_same<std::size_t, std::uint64_t>::value << endl;
}

On my 64bit MacOS system with clang 3.8 it gives the following output:

SIZE:
sizeof(unsigned long)      = 8
sizeof(unsigned long long) = 8
sizeof(std::size_t)        = 8
sizeof(std::uint64_t)      = 8

SIGNED?
false
false
false
false

SAME?
false
true
false
false
true
false

So I have four 64bit unsigned integer types, but they are actually referring to only two different types. There are more candidates, like std::uintptr_t, std::ptrdiff_t, and std::uint_fast64_t, and I don't even know if that list is complete. The definition of which types are the same looks arbitrary to me, although I see that for the exact problem I am facing we want long and long long to be treated as different.

Question: Is there any way to implement the above function for an exhaustive list of different integer types so that I don't run into trouble on a different platform? If so, what's this list? If no, what's the best way to achieve the desired behavior (handling of all integer types, meaningful default behavior for unknown types, portable solution)?

Upvotes: 2

Views: 379

Answers (2)

lisyarus
lisyarus

Reputation: 15567

Specializing for all integral types is generally a very bad idea. Based on the discussion in the comments, I assume that static dispatch on the size & signedness of types would suit you. Here's how it can be done:

#include <iostream>
#include <type_traits>

template <typename T, unsigned Size, bool Signed>
struct foo_impl;

template <typename T>
struct foo_impl<T, 1, false>
{
    static void foo (T & x) { std::cout << x << " is unsigned 8-bit" << std::endl; }
};

template <typename T>
struct foo_impl<T, 1, true>
{
    static void foo (T & x) { std::cout << x << " is signed 8-bit" << std::endl; }
};

template <typename T>
struct foo_impl<T, 2, false>
{
    static void foo (T & x) { std::cout << x << " is unsigned 16-bit" << std::endl; }
};

// more specializations ...

template <typename T>
void foo (T & x)
{
    // dispatch based on size & signedness
    foo_impl<T, sizeof(T), std::is_signed<T>::value>::foo(x);
}

int main ( )
{
    char a = 5;
    std::uint8_t b = 6;
    unsigned short c = 7;

    foo(a);
    foo(b);
    foo(c);
}

Live on ideone

Upvotes: 2

Mark B
Mark B

Reputation: 96291

Since you're trying to do something different for each specific integral type, even if you could enumerate all the types in your program you couldn't make up the specific special logic.

Instead what I'm going to suggest is implement the special logic for say short, int, unsigned, etc as you need, and put a static assert that is_integral<T> is false in your main template, with a message that it's being called with an unsupported integral type that needs to have custom logic determined and written.

Upvotes: 3

Related Questions