mardi 27 octobre 2020

Passing inline temporary class in c++ needs to be const. How to work around this

I want to write portable code in c++11 for different CPU's (actually MCU's). As some CPU's does not support reading program data directly through it's memory address space (such as Atmel AVR), I need a solution that calls a function either with a direct address, or with a custom made Stream pointer to read the data trough some external storage.

Consider this code as the custom library:

class IStream
{
    public: virtual char ReadChar();
};

class ConstMemoryStream : public IStream
{
    const char* Position;

    public: ConstMemoryStream(const char* startAddress)
    {
        Position = startAddress;
    }

    public: char ReadChar() override
    {
        return *Position++;
    }
};

void Send(char data) { } // Send data to serial port

Now, I want to implement a function that takes either a memory address, or a Stream to read the data from:

// const parameter is needed here, otherwise error: invalid initialisation of non-const reference of type 'IStream&' from an rvalue of type 'IStream'
void PrintMessage(const IStream& stream)
{
    while (true) // TODO: end condition
        //Send(stream.ReadChar());  // this gives an error because i need to use a const parameter: passing 'const IStream' as 'this' argument discards qualifiers
        Send( ((IStream*)&stream)->ReadChar() );  // this works, this actually bypass the error above. IS THIS OK?????
}

void PrintMessage(char* address); // overload to use memory instead of stream. implementation not important here

Next, I want to call PrintMessage with a Stream, but this stream needs to be created inline, and is not needed anymore outside the PrintMessage function:

int main(void)
{
    // Requirement: ConstMemoryStream needs to be created and passed INLINE PrintMessage
    PrintMessage(ConstMemoryStream("Hello!")); // This works only if i put const in PrintMessage parameter.
}

All the code above compiles and works, but my main concern is that I need to use a const parameter in the PrintMessage function (otherwise I get an error). Because of this, I need to do an ugly cast:

Send( ((IStream*)&stream)->ReadChar() );

This basically makes the parameter non-const to avoid the error. But is there a better solution to do this "legally"?

The stream instance itself cannot be const because it advances it's position internally, but c++ requires to pass it as const because it's an inline temporary variable which is always considered as an rvalue.

I don't see any harm from a temporary variable to modify itself, after the PrintMessage function return it's discarded anyway.

The inline requireWhat I finally want to do is this:

#ifdef CPU_AVR
    #define CSTR(str) ConstMemoryStream(PROGMEM str) // the PROGMEM attribute puts the text in a separate space not accessible in regular memory
#elif defined CPU_SAM
    #define CSTR(str) (char*)str
#endif

int main2(void)
{
    // If the CPU does not support direct address mapping to it's FLASH space, pass a stream instead of a direct memory pointer
    PrintMessage(CSTR("Hello"));
}

Any idea on how to do this properly without casting to discard the error? Or is the current code above acceptable?

Aucun commentaire:

Enregistrer un commentaire