samedi 10 juillet 2021

Using Reinterpret_Cast in a Constexpr Function

To my understanding C++11 specifically designates that reinterpret_cast cannot be used within a constant expression. The reason (again to my understanding) is that the compiler cannot interpret the validity of the conversion. With that being said, there does seem to be some level of trickery that can be used to allow the function to compile even when using a reinterpret_cast statement.

I have a situation where a single array of bytes within a parent class can be reinterpreted based on which subclass I want the data to represent at the time.

Within the code I have a constexpr which returns a reference to the subclasses member variable representation within the array, in this case a uint32_t variable. Using reinterpret_cast<uint32_t&>() the code does not compile with the compiler declaring that reinterpret_cast cannot result in a constant expression. However I can get the code to compile by wrapping the function within a template or by using a trivial ternary expression.

The example code below contains a macro labelled compBranchSwitch which allows you to quickly switch between compilation scenarios for convenience.

#include <cstdint>
#include <cstddef>
#include <array>
#include <iostream>

#define compBranchSwitch 0          //Switch to determine which branch to compile: 2 - With template function, 1 - With ternary operator, 0 - Without any trickery (should not compile)

struct Attributes {
    static std::array<char, 4> membersArray;

    struct Subclass {
        uint32_t num;

        static constexpr uint16_t offsetNum() { return offsetof(Subclass, num); }

#if compBranchSwitch == 2
        template<bool nothing>      //Unused template parameter that circumvents reinterpret_cast being unusable within a constexpr.
        static constexpr uint32_t& LoadNum() { return reinterpret_cast<uint32_t&>(membersArray[offsetNum()]); }

#elif compBranchSwitch == 1
        static constexpr uint32_t& LoadNum() { return (true ? reinterpret_cast<uint32_t&>(membersArray[offsetNum()]) : reinterpret_cast<uint32_t&>(membersArray[offsetNum()])); }

#else
        static constexpr uint32_t& LoadNum() { return reinterpret_cast<uint32_t&>(membersArray[offsetNum()]); }
#endif

        static inline void SaveNum(const uint32_t& newTest) { std::memcpy(&membersArray[offsetNum()], &newTest, sizeof(newTest)); }
    };
};

std::array<char, 4> Attributes::membersArray;

void main() {

    Attributes::Subclass::SaveNum(32);

#if compBranchSwitch == 2
    std::cout << Attributes::Subclass::LoadNum<true>();
#else
    std::cout << Attributes::Subclass::LoadNum();
#endif
}

The questions I have are:

  • Should I be worried or at all hesitant about using any of the tricks above to get the program to compile?
  • Is there a better work around to getting reinterpret_cast to work within a constant expression?
  • Just because reinterpret_cast is not allowed within a constant expression will the compiler still likely evaluate it at compile time under heavy optimization flags?

If it is helpful I am compiling under C++17 and using Visual Studio.

A closely related post on stackoverflow I found helpful for information in regards to the C++11 draft for constant expressions and in discovering the ternary operator trick can be found here.

Aucun commentaire:

Enregistrer un commentaire