Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

is bit_cast without compiler support for constexpr memcpy possible?

I had heard that std::bit_cast will be in C++20, and I am slightly puzzled about the conclusion that implementing it necessarily requires special compiler support.

To be fair, the argument I have heard is that the implementation performs a memcpy operation, and memcpy is not typically constexpr, while std::bit_cast is supposed to be, so making std::bit_cast constexpr supposedly requires compiler support for a constexpr-compliant memcpy operation.

I was wondering, however, if it was possible to implement a compliant bit_cast (ie, defined behavior, to same extent that using memcpy would have had defined behavior) without actually invoking memcpy at all.

Consider the following code:

template<typename T, typename U> 
inline constexpr T bit_cast(const U & x) noexcept {
    static_assert(std::is_trivial<T>::value && std::is_trivial<U>::value, "Cannot use bit_cast with non-trivial data" );
    static_assert(sizeof(T) == sizeof(U), "bit_cast must be used on identically sized types");
    union in_out {
        volatile U in;
        volatile T out;

        inline constexpr explicit in_out(const U &x) noexcept : in(x)
        {
        }

    };
    return in_out(in_out(x)).out;
}

Volatile members are used here to force the compiler to emit the necessary code that would write or read from the members, disabling optimizations, and while I know ordinarily assigning to one member of a union and reading from another in the same union is undefined behavior, the C++ standard does appear to allow reading from any member of a union IF it has been bytewise copied from another instance of the exact same union. In the above code, this is effectively accomplished by explicitly calling the default copy constructor on the newly constructed instance that happens to intialize the in data member. Since the above union contains all trivial types, calling the default copy constructor on it amounts to a bytewise copy, so reading from the out member of the newly constructed instance should not still be undefined behavior, should it?

Of course, it's entirely possible that I'm missing something extremely obvious here... I certainly can't claim to be any smarter than the people who develop these standards, but if someone can tell me exactly which undefined behavior I am invoking here, I'd really like to know about it.

like image 977
markt1964 Avatar asked Dec 23 '22 21:12

markt1964


2 Answers

One of the things you're not allowed to do during constant evaluation is, from [expr.const]/4.9:

an lvalue-to-rvalue conversion that is applied to a glvalue that refers to a non-active member of a union or a subobject thereof;

Which is what your implementation does, so it's not a viable implementation strategy.

like image 61
Barry Avatar answered Dec 25 '22 22:12

Barry


Volatile members are used here to force the compiler to emit the necessary code

If you need to write volatile to make sure the compiler doesn't optimize out your code then your code is not ok. A compiler cannot modify the observable behavior of a valid code. If what you wanted to do (sans volatile) would have been defined behavior, the compiler would not have been allowed to optimize out the writes and reads you want to force with volatile.

The UB comes from the fact that you are only allowed to read the active member of an union (in in your example) but you read the inactive one (out in your example).

like image 21
bolov Avatar answered Dec 26 '22 00:12

bolov