-2

Plz check following c++ code: (nothing special, should be compliant to c++ 2nd edition from 1991)

    class C
    {
            // also defines all the methods called in template method below with the obvious name and args.
    
            public: template<typename TEnum> void SetNullableEnumValuePtr(CNullable<TEnum>* aEnumPtr)
            {
                if(sizeof(TEnum) == 4)
                {
                    this->SetNullableUInt32Ptr(reinterpret_cast<CNullable<UInt32>*>(aEnumPtr));
                }
                else if (sizeof(TEnum) == 2)
                {
                    this->SetNullableUInt16Ptr(reinterpret_cast<CNullable<UInt16>*>(aEnumPtr));
                }
                else if (sizeof(TEnum) == 1)
                {
                    this->SetNullableUInt8Ptr(reinterpret_cast<CNullable<UInt8>*>(aEnumPtr));
                }
                else
                {
                    this->FailEnumSize();
                }
            }
    }

basic conditions class CNullable follows the well known, basic nullable pattern implemented i.e. for c# in certain frameworks but could also be any template with one argument.

as the names imply, TEnum is used for different enum types. i.e. enum FooEnum { foo };

For different platforms/compilers it is true, that with for the same enum type it goes to different if branches: On Win32 all enums are compiled with a size of 4 bytes always. On my MCU it compiles to uint8, if the values of the enum field implies to do so.

1. Not my focus: i'm not sure, if the c++ standard allows compliant compilers to generate enums with different sizes in respect of the required size but i think it is said, that it is compliant.

2. not my focus: it is also obvious that a cleaner / more robust solution would generate explicit template instances for the function for ALL used enum types. however i might do this and hope the compiler/optimizer is smart/compliant enough not to generate extra code for all types.

3. Not my focus: i know that reinterpret_cast can be evil but there is a reason the c++ standard defines it. so please stop downrating my questions just because you dont like it or dont understand it.

4. Not my focus: i know there are certain websites, you can test code on different compilers.

5. would help too: also if you have a better idea how to treat this coding issue you're welcome.

6. My focus:

i wanna focus this question on the issue, if the c++ standard [1] (or any other standard) defines rule(s), that the templates instances for intXX and uintXX (Where XX is the bit width and u indicates a unsigned version of the native integer) must forcefully compile to a code where a reinterpret_cast from CNullable<unsigned intXX> to CNullable<intXX> is guaranteed to run properly. it seems to be likely that a straight forward implementation of the tool chain may not cause problems in this case, but i got used to be carefull with such assumptions.

conclusion: if one could give me the reference(s) to chapter inside the (c++) or other standard treading these issues (1 to 6) - that would be great.

thank you!

[1] Please understand the sad reality, that the platform i work on does even not have a ready compiler specification. so it is quite trial and error and i wanna understand the techniques behind to get a better feeling for it - because treading the issue deterministic seems not to be possible since the compiler reference does not give a clear statement what standard it supports: They stated an ETSI standard rather than an iso c++ standard and they didnt give enough info to find the documentation of the standard - no version number, no document number, no document name. when i search for the info given in the compiler documentation on the ETSI page i get > 24.000 results (!) XD

PS: Edit: I request the info: a) Which standards / specifications ensure that this code runs properly?

b) Does anyone have a clean solution for an abstraction using different template instances for different enum types?

seebee
  • 1
  • 3
  • 3
    This is most likely undefined behavior, and code smell. I'm confident that the task at hand can be accomplished without any forced casts in a perfectly type-safe and bug-free manner, using any number of approaches in C++ that are used in this situation. However, without more complete information it's not possible to suggest the specific solution. – Sam Varshavchik Oct 05 '22 at 14:37
  • 2
    The cast it legal, but dereferencing the resulting pointer is not. – HolyBlackCat Oct 05 '22 at 14:38
  • 2
    You don't need the if-else chain. Use `std::underlying_type_t`. But then you'll also need to overload/template `SetNullable` for different integral types. – HolyBlackCat Oct 05 '22 at 14:39
  • 1
    In general case `C` and `C` are unrelated type. – Jarod42 Oct 05 '22 at 14:47
  • @Jarod42: The Standard makes *every* case of `C` and `C`, where `C` is an actual class template and `T1` and `T2` are not the exact same type, unrelated. Even if `T1` and `T2` are representation-compatible. – Ben Voigt Oct 05 '22 at 14:57
  • One would however be able to define some helper template classes and then a templated type alias `Nullable` such that multiple template parameters alias back to the same type, and this might be sufficient for OP, or might break other usages of `Nullable` – Ben Voigt Oct 05 '22 at 15:01
  • @HolyBlackCat. Thanks for the advice. but pretty sure there is no stl for my platform. – seebee Oct 05 '22 at 15:11
  • `` is header-only, so it shouldn't matter. Even if you don't have the actual header, your compiler must have a magic builtin for implementing it, which you can use directly. What *is* your platform? – HolyBlackCat Oct 05 '22 at 15:12
  • @HolyBlackCat: i understand dereferncing is not legal in respect to polymorphism but CNullable is not polymorph. – seebee Oct 05 '22 at 15:14
  • @Holyblackcat: My Platform ARM® Compiler v5.06 for μVision® and it yet doesnt officially support any iso c or c++ standard. (read the last note [1]) on my original qustion. so i doubt if stl could work (I'd love to use std::list or std::map but my application is asap design (as static as possible) because i dont trust the memory manager to handle fragmented memory. – seebee Oct 05 '22 at 15:17
  • Dereferencing is illegal simply because you cast to a different type. There are some exceptions, but likely none of them apply here. – HolyBlackCat Oct 05 '22 at 15:23
  • @Ben Voigt: Will read about c++ 11 type alias and try to get your solution. But i doubt the compiler can do it. I'll also rethink for an easy solution in c++ 1986. That is pretty save and i have a code generator fed by a mdl which i can extend for any pattern to create simple and save code even this compiler supports ;-) – seebee Oct 05 '22 at 15:27
  • @Jarod42, HolyBlackCat: Totally agree that T1 is a different type than T2 and therefor its "illegal". but plz remember we're talking about c++ practice and not oo theory. With that Mindset there would be no MS COM, no dlls, etc. c++ is not a high level language and if it would be, it would just not be possible to do so. Maybe that changed with c++ 11 a bit. if the types are defined equally (including really everything like calling conventions etc.) there should be no way it can "not work". nevertheless there are good reasons not to feel calm with it. gets sophisticated quickly... – seebee Oct 05 '22 at 15:46
  • @BenVoigt: I meant that user can add relationship, as for example, something like `template requires(std::is_enum_v) class C : C> {};`. – Jarod42 Oct 05 '22 at 17:23
  • @Jarod42: Ahh, that's true. – Ben Voigt Oct 05 '22 at 17:42
  • Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer. – Community Oct 05 '22 at 19:14
  • The first ISO C++ standard is only in 1998. I believe that C++ in 1986 didn't even have `reinterpret_cast`, for example. And templates, I am not sure. – prapin Oct 05 '22 at 21:01
  • @prapin: 1985: The C++ Programming Language, 1st edition, 1991: The C++ Programming Language, 2nd edition (including templates) Source: https://en.cppreference.com/ - sorry my fault. As i said there even is no clear statement about the supported c++ version in my compiler. thats why i'm so defensive. – seebee Oct 06 '22 at 16:45

1 Answers1

0

Ok think i i have a clean solution. It uses a wrapper pattern to hide the abstraction of concrete value types behind a virtual method.

I hope that is enough code to get the intention. I also put a very simple example of how i use it.

I guess in newer c++ version there are some stl elements which can replace the classes i wrote. But i need it for a very old c++ standard, which even isn't iso.

I really hope there are no errors since not all symbols are defined. but the essential stuff shall be shown in this example implementation.

// platform dependent UInt32 type.
typedef unsigned __int32 UInt32;

template<typename T>
class CNullable
{
        public: bool HasValue;
        public: T Value;
        public: CNullable(): Value(), HasValue(false) {}
        public: CNullable(const T& aValue): Value(aValue), HasValue(true) {}
        public: template<typename T1>
        CNullable<T1> To() const
        {
            return this->HasValue ? CNullable<T1>(static_cast<T1>(this->Value)) : CNullable<T1>();
        }    
        CNullable<UInt32> ToU32n() const
        {
            return this->To<UInt32>();
        }
};

// U32-Nullable type
typedef CNullable<UInt32> UInt32n;

// U32-Nullable argument type.
typedef const CNullable<UInt32>& UInt32na;


// Some kind of variant value that can store different types of values pointing into an (intended) statically allocated model 
class CValue
{

    // Report failure. Break debuger, raise exception, log error - whatever.
    private: static void Fail(const char* aTextPtr);

    // Sets the value in the (intended) statically allocated datamodel
    // Simple example with a UInt32 as new value
    public: void SetTargetValue(UInt32na rhs);

    // Sets the value in the (intended) statically allocated datamodel
    // May use a visitor pattern (GOF) for resolving assigned types, but thats not the topic here.
    public: void SetTargetValue(const CValue& rhs);

    // Ensures that object is not sealed an fails if so.
    private: bool CheckNotSealed();

    // Allows to change to sealed state to protect the object agains modification.
    // protection for "as static as possible"-memory-design
    public: void Seal();

    // Base class for Wrappers
    class CValueWrapper
    {
    public: virtual ~CValueWrapper() {}
    // Converts the current value as an U32.
    public: virtual UInt32n GetInterpretedU32n() { Fail("Data conversion not supported."); return UInt32n(); }

    // converts the new value from an U32 and sets the value
    public: virtual void SetInterpretedU32n(UInt32na aU32n) { Fail("Data conversion not supported."); }
    };

    // Wrappers Base class for any enum related type.
    class CEnumWrapperBase : public CValueWrapper
    {
    public: virtual UInt32n GetU32n() const = 0;
    public: virtual void SetU32n(UInt32na aU32n) const = 0;
    public: virtual UInt32n GetInterpretedU32n() { return this->GetU32n(); }
    public: virtual void SetInterpretedU32n(UInt32na aU32n) { this->SetU32n(aU32n); }
    };

    // Wrapper base class for values of type = Nullable<TEnums> 
    template<class TEnum> class CNullableEnumWrapper : public CEnumWrapperBase
    {
        private: CNullable<TEnum>* mNullableEnumPtr;
        public: CNullableEnumWrapper(CNullable<TEnum>* aNullableEnumPtr)
            :
            mNullableEnumPtr(aNullableEnumPtr)
        {
        }
        public: virtual UInt32n GetU32n() const
        {
            return this->mNullableEnumPtr ? this->mNullableEnumPtr->ToU32n() : UInt32n();
        }
        public: virtual void SetU32n(UInt32na aU32n) const
        {
            if (this->mNullableEnumPtr)
            {
                *this->mNullableEnumPtr = aU32n.To<TEnum>();
            }
        }
    };

    // Wrapper base class for values of type = Nullable<TEnums> 
    template<class TEnum> class CEnumWrapper : public CEnumWrapperBase
    {
        public: CEnumWrapper(TEnum* aEnumPtr)
            :
            mEnumPtr(aEnumPtr)
        {
        }
        private: TEnum* mEnumPtr;
        public: virtual UInt32n GetU32n() const
        {
            return this->mEnumPtr ? static_cast<UInt32>(*this->mEnumPtr) : UInt32n();
        }
        public: virtual void SetU32n(UInt32na aU32n) const
        {
            if (this->mEnumPtr
                && aU32n.HasValue)
            {
                *this->mEnumPtr = static_cast<TEnum>(aU32n.Value);
            }
        }
    };

    // Allows to lock instantian of wrapper objects. 
    // In my bare metal application all wrappers are created on application startup 
    // and stay allocated until the device is switched of (by disconnecting power)
    // [ThreadStatic]
    public: static bool InstanciateValueWrapperEnabled;

          // Set pointer to enum value (intended to be allocated in a static model)
    public: template<class TEnum> void SetEnumValuePtr(TEnum* aEnumPtr)
    {
        if (this->InstanciateValueWrapperEnabled)
        {
            if (this->CheckNotSealed())
            {
                this->SetValueWrapperPtr(new CEnumWrapper<TEnum>(aEnumPtr), true);
            }
        }
        else
        {
            Fail("Invalid operation.");
        }
    }

          // Set pointer to nullable<enum> value (intended to be allocated in a static model)
    public: template<class TEnum> void SetNullableEnumValuePtr(CNullable<TEnum>* aEnumPtr)
    {
        if (this->InstanciateValueWrapperEnabled)
        {
            if (this->CheckNotSealed())
            {
                this->SetValueWrapperPtr(new CNullableEnumWrapper<TEnum>(aEnumPtr), true);
            }
        }
        else
        {
            Fail("Invalid operation.");
        }
    }


          // Sets the member var and data type to 'CValueWrapper' (may support some types natively without a wrapper object)
    public: void SetValueWrapperPtr(CValueWrapper* aValueWrapperPtr, bool aOwning);

};

// Model Base Code
//////////////////////////////////////
/// Application specific code

enum FooEnum { FooEnum_Val1 };

// Reads data from StdIn, uart, or whatever.
UInt32 ReadU32();

// Process data, for example output calculated data to another hardware interface.
void Process(CValue** aValuePtrs);

// Simple example of how its being used.
// in real environment its likely to encapsulate a set of CValue objects 
// in a ModelInstance-Object and build it by a ModelDefinition object parsing a model definiton language (mdl) 
// or adapt generated code. etc. etc...
void main()
{
    // Define the static model:
    static FooEnum gFooEnum;
    static CNullable<FooEnum> gNullableFooEnum;

    // Define the values to access the static model
    CValue aFooEnumVal;
    CValue aNullableFooEnumVal;

    // Begin of init:
    CValue::InstanciateValueWrapperEnabled = true;
    aFooEnumVal.SetEnumValuePtr(&gFooEnum);
    aNullableFooEnumVal.SetNullableEnumValuePtr(&gNullableFooEnum);
    CValue::InstanciateValueWrapperEnabled = false;
    // End of init

    // Create an array of values
    const UInt32 aPropertyCount = 2;
    CValue* aPropertyPtrs[aPropertyCount] =
    {
        &aFooEnumVal,
        &aNullableFooEnumVal
    };

    for (UInt32 aIdx = 0; aIdx < aPropertyCount; ++aIdx)
    {
        aPropertyPtrs[aIdx]->Seal();
    }

    // Very simple and unsave data receiption loop.
    while (true) 
    {
        UInt32 aPropertyIdToSet = ReadU32(); // The property id to receive.
        UInt32 aNewValHasValue = ReadU32();  // Wether the value is defined
        UInt32 aNewValValue = ReadU32();     // The value
        UInt32n aNewVal                      // Nullable for NewValue 
            = aNewValHasValue                // if value is defined
            ? UInt32n(aNewValValue)          // Create a nullable which has a value
            : UInt32n()                      // Create a nullable which has no value
            ;
        CValue* aValuePtr = aPropertyPtrs[aPropertyIdToSet]; // Get the value to receive.
        aValuePtr->SetTargetValue(aNewVal);                  // Set the value to the static model
        Process(aPropertyPtrs);                              // Process data newly received.
    }
}
seebee
  • 1
  • 3