Home > database >  Creating custom sizeof() that returns narrower types
Creating custom sizeof() that returns narrower types

Time:07-06

The Issue

sizeof returns size_t type, so when passed as argument to functions that takes in narrower types (e.g. unsigned char), implicit conversion occurs. In many cases these are 3rd party library functions, so their prototypes are beyond my control. Compilers are now typically smart enough to detect whether such conversions would really cause truncation and warn you about it, but some static code analyzers will still flag such cases out, leading to lots of false positives. Explicitly casting the result of sizeof typically resolves the analysis warnings but it would hide the compiler warnings, not to mention that it makes things clunky.

My Solution

template<class T1, class T2>
struct sizeofxx {
    static constexpr T2 value{ sizeof(T1) };
};

template <class T>
constexpr unsigned int sizeof32 = sizeofxx<T, unsigned int>::value;

template <class T>
constexpr unsigned short sizeof16 = sizeofxx<T, unsigned short>::value;

template <class T>
constexpr unsigned char sizeof8 = sizeofxx<T, unsigned char>::value;

Usage:

unsigned int foo = sizeof32<float>;
const char bar[255];
unsigned char foo3 = sizeof8<decltype(bar)>;

It relies on aggregate initialization to guard against narrowing conversion at compile time. So if I had used bar[256], the build fails.

Limitation

But as you can see, using it on variables is rather clunky (due to the need for decltype). Is there a simpler way to do this? I know one way is wrap it in a macro, but this would prevent IDEs like Visual Studio from helping you resolve the value when you mouseover it. Another way is to create a constexpr function:

template <class T1>
constexpr unsigned char sizeof8f(T1&) {
    return sizeof(T1);
}

But this means that when passed as an argument to functions, the value won't be resolved at compile-time (i.e. doing Func(sizeof8f(bar)) means the compiled code will actually call sizeof8f rather than using the value directly https://godbolt.org/z/9ETEjfrxo).

Any other suggestions on resolving the root issue (static code analysis warnings) are welcomed. And no, suppressing them is not feasible.

CodePudding user response:

For your specific problem, there need not be any runtime checks even on debug builds as some has suggested, since the value is itself a constexpr. You can write a simple utility to cast a value to the smallest type that is able to hold it.

template<size_t N>
inline constexpr auto minuint = []{
    if constexpr(N >= 1ull << 32)
        return N;
    else if constexpr(N >= 1ull << 16)
        return uint32_t(N);
    else if constexpr(N >= 1ull << 8)
        return uint16_t(N);
    else
        return uint8_t(N);
}();

On the other hand, no function or template can ever accept both expressions and types. The only possible way to imitate sizeof behaviour is to use a macro.

#define Sizeof(x) minuint<sizeof(x)>

With this, you never get false warnings on narrowing conversions: if there is a warning, you are doing something wrong.

CodePudding user response:

The compiler would most likely optimize out the function, even without constexpr example on godbolt


you may want to use T&& or const T& so it bind to an rvalue, depend on what you want.

template<typename size_type,typename T>
constexpr size_type sizeofxx(T&& t){
    return sizeof(T);
}

CodePudding user response:

How about this

template<typename size_type, typename object_type>
constexpr size_type
sizeof_as(object_type& object) {
    // TODO: static_assert(...)
    return size_type{sizeof object};
}

auto foo = ...; // some object
auto size = sizeof_as<uint8_t>(foo);

First template parameter is given explicitly, second one deduced from the argument.

Maybe add to that another function that doesn't require an object but just its type, similar to the second sizeof syntax:

template<typename size_type, typename object_type>
constexpr size_type
sizeof_as() {
    // TODO: static_assert(...)
    return size_type{sizeof (object_type)};
}
  • Related