std::bit_cast with std::array

前端 未结 3 1889
孤独总比滥情好
孤独总比滥情好 2021-02-19 09:29

In his recent talk “Type punning in modern C++” Timur Doumler said that std::bit_cast cannot be used to bit cast a float into an unsigned char[4]

相关标签:
3条回答
  • 2021-02-19 09:51

    Yes, this works on all major compilers, and as far as I can tell from looking at the standard, it is portable and guaranteed to work.

    First of all, std::array<unsigned char, sizeof(float)> is guaranteed to be an aggregate (https://eel.is/c++draft/array#overview-2). From this follows that it holds exactly a sizeof(float) number of chars inside (typically as a char[], although afaics the standard doesn't mandate this particular implementation - but it does say the elements must be contiguous) and cannot have any additional non-static members.

    It is therefore trivially copyable, and its size matches that of float as well.

    Those two properties allow you to bit_cast between them.

    0 讨论(0)
  • 2021-02-19 09:58

    The accepted answer is incorrect because it fails to consider alignment and padding issues.

    Per [array]/1-3:

    The header <array> defines a class template for storing fixed-size sequences of objects. An array is a contiguous container. An instance of array<T, N> stores N elements of type T, so that size() == N is an invariant.

    An array is an aggregate that can be list-initialized with up to N elements whose types are convertible to T.

    An array meets all of the requirements of a container and of a reversible container ([container.requirements]), except that a default constructed array object is not empty and that swap does not have constant complexity. An array meets some of the requirements of a sequence container. Descriptions are provided here only for operations on array that are not described in one of these tables and for operations where there is additional semantic information.

    The standard does not actually require std::array to have exactly one public data member of type T[N], so in theory it is possible that sizeof(To) != sizeof(From) or is_­trivially_­copyable_­v<To>.

    I will be surprised if this doesn't work in practice, though.

    0 讨论(0)
  • 2021-02-19 10:04

    Yes.

    According to the paper that describes the behaviour of std::bit_cast, and its proposed implementation as far as both types have the same size and are trivially copyable the cast should be successful.

    A simplified implementation of std::bit_cast should be something like:

    template <class Dest, class Source>
    inline Dest bit_cast(Source const &source) {
        static_assert(sizeof(Dest) == sizeof(Source));
        static_assert(std::is_trivially_copyable<Dest>::value);
        static_assert(std::is_trivially_copyable<Source>::value);
    
        Dest dest;
        std::memcpy(&dest, &source, sizeof(dest));
        return dest;
    }
    

    Since a float (4 bytes) and an array of unsigned char with size_of(float) respect all those asserts, the underlying std::memcpy will be carried out. Therefore, each element in the resulting array will be one consecutive byte of the float.

    In order to prove this behaviour, I wrote a small example in Compiler Explorer that you can try here: https://godbolt.org/z/4G21zS. The float 5.0 is properly stored as an array of bytes (Ox40a00000) that corresponds to the hexadecimal representation of that float number in Big Endian.

    0 讨论(0)
提交回复
热议问题