tstenner
tstenner

Reputation: 10311

Cast from long to same-sized int fails

I try to convert an array of long to an array of same-sized ints (godbolt):

#include <cstdint>
#include <cstddef>
#include <cstring>

template<int> struct sized_int_ {};      
template<> struct sized_int_<8> { typedef int64_t type; };   
template<> struct sized_int_<4> { typedef int32_t type; };
typedef sized_int_<sizeof(long)>::type long_t;              

void funcA(int32_t* array, std::size_t s); // defined somewhere else
void funcA(int64_t* array, std::size_t s); // defined somewhere else

// technically illegal cast
void funcB(long* array, std::size_t s) {
    funcA(static_cast<long_t*>(array), s);
}

// memcpy
void funcC(long* array, std::size_t s) {
    long_t* tmp = new long_t[s];
    memcpy(tmp, array, s*sizeof(long_t));
    funcA(tmp, s);
    delete[] tmp;
}

int main() {
    long x[] = {2, 3};
    static_assert(sizeof(long_t)==sizeof(long), "Sizes don't match");
    funcB(x, 2);
    funcC(x, 2);
    return 0;
}

g++ happily accepts the code, but clang on MacOS doesn't (error: static_cast from 'long *' to 'long_t *' (aka 'long long *') is not allowed). reinterpret_cast works in both cases, but it also allows me to cast from int32_t* to int64_t*.

The correct way to allocate a temporary array and copy the data to it doesn't get optimized out by at least clang and gcc with -O2.

How can I cast long* to the corresponding intX_t* safely?

Upvotes: 1

Views: 199

Answers (2)

eerorika
eerorika

Reputation: 238441

How can I cast long to the corresponding intX_t safely?

You don't need to cast. The conversion is implicit:

long x = 2;
long_t y = x;

But you can use static cast if you want to be explicit:

long_t y = static_cast<long_t>(x);

The pointer trickery implies that you may want to deal with the object in-place. This of course requires that the types have the same representation, which is fairly reasonable assumption although not guaranteed. But even the assumption not technically sufficient for the indirection through reinterpret_cast to be well defined according to the standard.

You can technically reuse the storage by creating an object of desired type:

long_t temp = x;
long_t* reused_x = new(&x) long_t(temp);

After reusage, you can convert the pointer to x on the fly if you can't store the one returned by placement new by laundering. Note that laundering is not sufficient without the placement new above.

long_t* converted = std::launder(reinterpret_cast<long_t*>(&x));

Same can be done in a loop with arrays:

template<class T, class F> // types To and From
T* reuse_array(F* first, F* last, T* d_first) {
    for (F* ptr = first; ptr != last; ++d_first, (void) ++ptr) {
        T value = *ptr;
       ::new (ptr) T(value);
    }
   return std::launder(reinterpret_cast<T*>(first));
}

This can be done even with strctures using std::memcpy as long as they are trivially copyable and destructible. A reasonably good optimiser should elide the copies. Note that the lifetime of x has ended and may not be used anymore.

There is a proposal to introduce std::bless into the language which should remove the need for explicit object creation in cases like this.

Upvotes: 2

Swift - Friday Pie
Swift - Friday Pie

Reputation: 14688

Technically speaking you don't need any casting at all and if there is no need in function wrapper, it's better to template it, as one of intX_t is an alias of one of integral types and overloading of function works due to implicit cast.

Upvotes: 0

Related Questions