更改类型而不更改位

时间:2015-02-20 16:08:39

标签: c++ c++11 types casting

我想将一个堆栈变量和reinterpret cast取为一个大小相同的无符号整数类型。例如,我可能想要取double值并将其强制转换为uint64_t,并且不会修改位。我想以通用的方式做到这一点。

如果我正在处理指针,我会使用reinterpret_cast<uint64_t*>(double_ptr)

我提出了一个解决方案,它在reinterpret_cast上使用了一个肮脏的黑客,并且是有效的,但它需要相当多的元编程来获得相当简单的结果。

问题:有更好的方法吗?我确信有,并且我使这变得比需要更复杂。

我确实考虑过使用类型为T且大小合适int_t的模板化联合,但这看起来甚至比较粗糙,似乎与未定义的行为有关。

编辑我知道标准没有指定double应该是64位,正如评论中所指出的那样。但是使用通用方法,我将能够获得与double相同大小的无符号整数类型,无论大小如此。

#include <iostream>

template <typename T, std::size_t S>
struct helper {};

template <typename T>
struct helper<T, 1> {
    using type = uint8_t;
};
template <typename T>
struct helper<T, 2> {
    using type = uint16_t;
};
template <typename T>
struct helper<T, 4> {
    using type = uint32_t;
};
template <typename T>
struct helper<T, 8> {
    using type = uint64_t;
};

template <typename T>
using int_type = typename helper<T, sizeof(T)>::type;

template <typename T>
int_type<T> caster(T value) {
    int_type<T> v;
    *reinterpret_cast<T*>(&v) = value;
    return v;
}

int main(void) {
    {
    auto val = caster(0.);
    static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    {
    auto val = caster(0.f);
    static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    {
    auto val = caster(-0.);
    static_assert(std::is_same<uint64_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    {
    auto val = caster(-0.f);
    static_assert(std::is_same<uint32_t, decltype(val)>::value, "no good");
    std::cout << sizeof(val)*8 << " " << val << std::endl;
    }

    return 0;
}

用gcc编译上面的代码给出:

> g++ --version
g++ (GCC) 4.8.2 20131016 (Cray Inc.)

> g++ -std=c++11 test.cpp && ./a.out
64 0
32 0
64 9223372036854775808
32 2147483648

2 个答案:

答案 0 :(得分:7)

如果由于违反别名限制(C ++ 11 3.10 / 10)而不希望出现未定义的行为,则需要将对象表示作为字符进行访问:

template <typename T>
int_type<T> caster(const T& value) {
    int_type<T> v;
    static_assert(sizeof(value) == sizeof(v), "");
    std::copy_n(reinterpret_cast<const char*>(&value),
                sizeof(T),
                reinterpret_cast<char*>(&v));
    return v;
}

高质量的编译器将优化副本。例如,该计划:

int main() {
    return caster(3.14f);
}

effectively optimizes to return 1078523331; on Intel processors

答案 1 :(得分:2)

std::conditional_tstd::enable_if_t之间我相信您可以将所有helperint_type定义压缩为自给自足的caster函数:

template <typename T>
auto caster(T value){return reinterpret_cast<std::conditional_t<sizeof(T) == sizeof(uint8_t),
                                                                uint8_t,
                                                                conditional_t<sizeof(T) == sizeof(uint16_t),
                                                                              uint16_t,
                                                                              conditional_t<sizeof(T) == sizeof(uint32_t),
                                                                                            uint32_t,
                                                                                            enable_if_t<sizeof(T) == sizeof(uint64_t),
                                                                                                        uint64_t>>>>&>(value);}

我已经验证了这适用于gcc 4.9.2和Visual Studio 2015,如果你只支持C ++ 11,尽管你仍然可以将它变成一个自给自足的caster函数:

template <typename T>
typename std::conditional<sizeof(T) == sizeof(uint8_t),
                          uint8_t,
                          typename conditional<sizeof(T) == sizeof(uint16_t),
                                               uint16_t,
                                               typename conditional<sizeof(T) == sizeof(uint32_t),
                                                                    uint32_t,
                                                                    typename enable_if<sizeof(T) == sizeof(uint64_t),
                                                                                       uint64_t>::type>::type>::type>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}

这将选择与您传递给它的类型uint*相同的sizeof并使用它。

我对std::enable_ifhere的解释可能对您有所帮助。

显然这对于​​大小为8,16,32或64位的类型非常有用,但是如果你想扩展它来处理其他东西,只需添加另一个conditional_t


如果您要传递8,16,32或64位类型,您可以在模板中获得较少的保护:

template <typename T>
auto caster(T value){return reinterpret_cast<std::tuple_element_t<size_t(log2(sizeof(T))), std::tuple<uint8_t,
                                                                                                      uint16_t,
                                                                                                      uint32_t,
                                                                                                      uint64_t>>&>(value);}

这适用于C ++ 14,C ++ 11等效于:

template <typename T>
typename std::tuple_element<size_t(log2(sizeof(T))), std::tuple<uint8_t,
                                                                uint16_t,
                                                                uint32_t,
                                                                uint64_t>>::type caster(T value){return reinterpret_cast<decltype(caster(value))&>(value);}

这比conditional_t / enable_if_t模板更不宽容,因为我正在为std::tupple编制索引。 size_t是一个整数类型,因此任何小于128位的任何大小的类型都将强制转换为有效的std::tuple索引。因此,例如,大小为3位的struct将被转换为uint16_t,而期望的结果可能是它无法编译。