Returning the Value from a pointer in a Class. Hoping that the pointer could point to anything

This might be advanced.

The Question How do I properly fix the errors and warnings in the following code (I added the errors and warnings as comments in the code for your convenience) The example is an additional concept I'm hoping to add to a much larger program that is currently functioning and usable.

The goal is to return the value of the variable that received its value as a return from the ReturnAnyValue() function. to be used as a single line of code.

// Simple Example
MyClass IAmConfused;
int OldValue = 0;
int NewValue = 0;
NewValue = IAmConfused.SetIntValue(&OldValue ).ReturnAnyValue();
// In know that OldValue and NewValue will be the same

I hope that you can help.

The Example code in question:
I place "???" in place of "uint16_t" where I know I messed up the most

class MyClass {
  public:
    ??? *pointerToValue; // this needs to hold a pointer to anything
    MyClass(); //Constructor
    MyClass & SetByteValue(uint8_t *data);
    MyClass & SetIntValue(uint16_t *data);
    uint8_t ReturnByteValue();
    uint16_t ReturnIntValue();
    ??? ReturnAnyValue(); // this needs to return any value type
};

MyClass::MyClass() {}

MyClass & MyClass::SetByteValue(uint8_t *data) {
  data[0] = 200; // something more complex happens here 
  pointerToValue = data; //Error: cannot convert 'uint8_t* {aka unsigned char*}' to 'uint16_t* {aka unsigned int*}' in assignment
  return *this;
}

MyClass & MyClass::SetIntValue(uint16_t *data) {
  data[0] = 42949; // something more complex happens here 
  pointerToValue = data;
  return *this;
}
uint8_t MyClass::ReturnByteValue() {  // 
  return ((uint8_t)pointerToValue[0]);
}
uint16_t MyClass::ReturnIntValue() {  // works
  return ((uint16_t)pointerToValue[0]);
}
// How do I return both the int and the byte with the ReturnValue() function
??? MyClass::ReturnAnyValue() {
  return (pointerToValue[0]);
}

MyClass IAmConfused;

void setup() {
  Serial.begin(115200);
  // put your setup code here, to run once:
  uint8_t ValueByte = 0;
  uint16_t ValueInt = 0;

  Serial.println((uint8_t)IAmConfused.SetByteValue(ValueByte).ReturnAnyValue()); //warning: invalid conversion from 'uint8_t {aka unsigned char}' to 'uint8_t* {aka unsigned char*}'
  Serial.println((uint16_t)IAmConfused.SetIntValue(ValueInt).ReturnAnyValue());  //warning: invalid conversion from 'uint16_t {aka unsigned int}' to 'uint16_t* {aka unsigned int*}'

  Serial.println(ValueByte);
  Serial.println(ValueInt);

  // I know I can use the original variables but it would be helpful when doing something like:
  if ((uint8_t)IAmConfused.SetByteValue(ValueByte).ReturnAnyValue() < 220) { //warning: invalid conversion from 'uint8_t {aka unsigned char}' to 'uint8_t* {aka unsigned char*}'
    Serial.println("Less than 220");
  }
}

void loop() {
  // put your main code here, to run repeatedly:
}

While I may be able to find the exact solution online, I am hoping to also find the best practice while I am at it. your help Is Much appreciated and welcomed. Thanks in advance
Z
PS. I am only looking for answers and not a bunch of criticism I'm sure there is a correct way and my hope is that what I'm trying to do is feasible and proper for programming or could be made to be. So suggestions links to examples and a direction I should consider pursuing will be most helpful.

One way would be to use templates. Assuming that the pointer type is constant for a class instance, the following should work.

template <class T>
class MyClass {
  // ...

  T* pointerToValue;

  // ...

  T* ReturnAnyValue();

Alternatively, you could use void pointers in combination with reinterpret_cast (read the remarks of @PieterP (post #4) though).

1 Like

Unless you know exactly what you're doing, you would probably be invoking undefined behavior using reinterpret_cast, see the rules outlined in reinterpret_cast conversion - cppreference.com.

It's very easy to shoot yourself in the foot when using void pointers, and they easily spread throughout the code base if not encapsulated properly.

1 Like

Not a criticism but.... that does not make any sense.

When you return something by value, a certain number of bytes are put on the stack to represent the data. This number of bytes obviously depends on the type of the data... it can't be both a byte (1 byte) and an int (2 or 4 bytes depending on the platform).

can you clarify exactly the use case ?

here is something with a union if your class can hold different types of data into an array and you want to return the last modified element and remember its type

1 Like

Excellent help.

now that I am using void* for my pointer and storing the data variable pointer into it.
What's the best practice for getting the value out with the correct data type?

class MyClass {
  public:
    void* pointerToValue;
    MyClass(); //Constructor
    MyClass & SetByteValue(uint8_t *data);
    MyClass & SetIntValue(uint16_t *data);
    reinterpret_cast  ReturnAnyValue();
};

MyClass::MyClass() {}

MyClass & MyClass::SetByteValue(uint8_t *data) {
  data[0] = 200; // something more complex happens here
  pointerToValue = static_cast<void*>(data);
  return *this;
}

MyClass & MyClass::SetIntValue(uint16_t *data) {
  data[0] = 42949; // something more complex happens here
  pointerToValue = static_cast<void*>(data);
  return *this;
}
// How do I return both the int and the byte with the ReturnValue() function
reinterpret_cast MyClass::ReturnAnyValue() {
  return (reinterpret_cast &pointerToValue);
}

Question 2
Should I use reinterpret_cast or static_cast?
...
@J-M-L
just looking at your post now. I would like to use unions My data tested will always be Unsigned and signed ints and bytes nothing else.

*Thanks again for all your advice. I am pursuing each of your suggestions as with success in this, I will greatly simplify my program.

Please keep them coming.

Z

what's the use case for having different storage types needs? can the data start being bytes, then change to be unsigned ints and then become signed ints or is the data fixed for a given instance of your class? in the latter case, using templates would probably help

alternatively you could store the data as long long int and be done with it :slight_smile:

The union option suggested isn't going to work because the value returned can't be a union it must be a value of the correct type.

@jfjlaros
I am favoring your suggestion, I just need to echo the value stored in the pointer received by the function and return it to be used

for example, I am retrieving a single bit over the i2c bus I store a 1 in the uint8_t pointer. not that I retrieved it once done I just want to use its value as a check

The library I wish to enhance: ZHomeSlice/Simple_Wire

example using Simple_Wire and my Simple_BNO055 libries:

// one of many macros This one test to see if the System calibration is complete it reads one byte and returns 2 bits starting at bit 7 to bit 6 (Backwards) form register 0x35
#define R_SYS_CALIB_STAT(Data)                PG(0).ReadBit(0x35, 2, 7, (uint8_t *)Data)  //   SYS Calib Status 0:3

//Example of its use:
Simple_BNO055 BNO;
// ... other code of course...
uint8_t CalStatus;
if(BNO.R_SYS_CALIB_STAT(&CalStatus).ReturnAnyValue() == 3) {
   Serial.println("Calibration Complete");
} else {
   Serial.print("Calibration at level:");
   Serial.println(CalStatus);
}

The ReturnAnyValue() function is what I am struggling with now. This class will become a part of the Simple_Wire class linked above.

Reposting code I am currently stuck at:


class MyClass {
  public:
    void* pointerToValue;
    MyClass(); //Constructor
    MyClass & SetByteValue(uint8_t *data);
    MyClass & SetIntValue(uint16_t *data);
    reinterpret_cast  ReturnAnyValue();
};

MyClass::MyClass() {}

MyClass & MyClass::SetByteValue(uint8_t *data) {
  data[0] = 200; // something more complex happens here
  pointerToValue = static_cast<void*>(data);
  return *this;
}

MyClass & MyClass::SetIntValue(uint16_t *data) {
  data[0] = 42949; // something more complex happens here
  pointerToValue = static_cast<void*>(data);
  return *this;
}
// How do I return both the int and the byte with the ReturnValue() function
reinterpret_cast MyClass::ReturnAnyValue() {  // **** Need help here
  return (reinterpret_cast &pointerToValue);
}

Thanks :slight_smile:
Z

As I said a function has only one returned type which defines how many bytes are reserved on the stack for this data. You can’t return different types.

You can return a void* pointer but you also need to return informatjon that will let the caller know what is the object really pointed at so that you can cast it back to what it really is. So you need an extra returned info, it was the purpose of my field attribute in my example

Humm, probably why I'm struggling with this... Could there be a way to have several functions with the same name and be overloaded in a way to return the correct value?

uint8_t MyClass::ReturnAnyValue(uint8_t *data) {  // **** Need help here
  return (data[0]);
}
uint16_t MyClass::ReturnAnyValue(uint16_t *data) {  // **** Need help here
  return (data[0]);
}

This is a thought I'm having...
Suggestions

You can't overload methods based on return type.
Overload resolution takes into account the function signature

1.3.11 signature
the information about a function that participates in overload resolution (13.3): its parameter-type-list (8.3.5) and, if the function is a class member, the cv-qualifiers (if any) on the function itself and the class in which the member function is declared….

1 Like

Thank you I google "function signature" and I now understand why my hopes were futile in the first place.
Now that I have a much clearer understanding,

Do you have any objections to this choice of resolving the solution?
Is there a better way?

This Compiles and returns expected results :grinning:

class MyClass {
  public:
    MyClass(); //Constructor
    MyClass & SetByteValue(uint8_t *data);
    MyClass & SetIntValue(uint16_t *data);
    uint8_t   ReturnAnyValue(uint8_t *data);
    uint16_t   ReturnAnyValue(uint16_t *data);
};

MyClass::MyClass() {}

MyClass & MyClass::SetByteValue(uint8_t *data) {
  data[0] = 200; // something more complex happens here
  return *this;
}

MyClass & MyClass::SetIntValue(uint16_t *data) {
  data[0] = 42949; // something more complex happens here
  return *this;
}

uint8_t MyClass::ReturnAnyValue(uint8_t *pointerToValue ) {
  return ( pointerToValue[0]);
}
uint16_t MyClass::ReturnAnyValue(uint16_t *pointerToValue ) {
  return ( pointerToValue[0]);
}
MyClass IAmConfused;

void setup() {
  Serial.begin(115200);
  // put your setup code here, to run once:
  uint8_t ValueByte = 0;
  uint16_t ValueInt = 0;

  Serial.println(IAmConfused.SetByteValue(&ValueByte).ReturnAnyValue(&ValueByte)); 
  Serial.println(IAmConfused.SetIntValue(&ValueInt).ReturnAnyValue(&ValueInt));  

  Serial.println(ValueByte);
  Serial.println(ValueInt);

  if (IAmConfused.SetByteValue(&ValueByte).ReturnAnyValue(&ValueByte) < 220) { 
    Serial.println("Less than 220");
  }
}

void loop() {
  // put your main code here, to run repeatedly:
}

Z

It would work indeed. I’m still unsure about the use case though :wink:

A Simple Example

my BNO_ReadMacros.h (And for writing BNO_WriteMacros.h) contains a series of macros that basically configure a simple read i2c function. Here is a list of macros that match the documented BNO055 pg 51 of the registry map:

#define R_AXIS_MAP_SIGN_X(Data)               PG(0).ReadBit(0x42, 1, 2, (uint8_t *)Data)  //   Remapped X axis sign
#define R_AXIS_MAP_SIGN_Y(Data)               PG(0).ReadBit(0x42, 1, 1, (uint8_t *)Data)  //   Remapped Y axis sign
#define R_AXIS_MAP_SIGN_Z(Data)               PG(0).ReadBit(0x42, 1, 0, (uint8_t *)Data)  //   Remapped Z axis sign

#define R_AXIS_MAP_CONFIG_X(Data)             PG(0).ReadBit(0x41, 2, 1, (uint8_t *)Data)  //   Remapped X axis value
#define R_AXIS_MAP_CONFIG_Y(Data)             PG(0).ReadBit(0x41, 2, 3, (uint8_t *)Data)  //   Remapped Y axis value
#define R_AXIS_MAP_CONFIG_Z(Data)             PG(0).ReadBit(0x41, 2, 5, (uint8_t *)Data)  //   Remapped Z axis value 

#define R_TEMP_SOURCE(Data)                   PG(0).ReadBit(0x40, 2, 1, (uint8_t *)Data)  //   TEMP_Source <1:0>

#define R_PWR_MODE(Data)                      PG(0).ReadBit(0x3E, 2, 1, (uint8_t *)Data)  //   Power Mode <1:0>

#define R_OPR_MODE(Data)                      PG(0).ReadBit(0x3D, 4, 3, (uint8_t *)Data)  //   Operation Mode <3:0>  Default 0x1C

//0x3C Reserved

#define R_UNIT_SEL(Data)                      PG(0).ReadByte(0x3B, (uint8_t *)Data)       //   Units Selection
#define R_UNIT_SEL_ORI_Android_Windows(Data)  PG(0).ReadBit(0x3B, 1, 7, (uint8_t *)Data)  //   ORI_Android_Windows
#define R_UNIT_SEL_TEMP_Unit(Data)            PG(0).ReadBit(0x3B, 1, 4, (uint8_t *)Data)  //   TEMP_Unit
#define R_UNIT_SEL_EUL_Unit(Data)             PG(0).ReadBit(0x3B, 1, 2, (uint8_t *)Data)  //   EUL_Unit
#define R_UNIT_SEL_GYR_Unit(Data)             PG(0).ReadBit(0x3B, 1, 1, (uint8_t *)Data)  //   GYR_Unit
#define R_UNIT_SEL_ACC_Uni(Data)              PG(0).ReadBit(0x3B, 1, 0, (uint8_t *)Data)  //   ACC_Uni

#define R_SYS_ERR(Data)                       PG(0).ReadByte(0x3A, (uint8_t *)Data)       //  System Error Code

#define R_SYS_STATUS(Data)                    PG(0).ReadByte(0x39, (uint8_t *)Data)       //  System Status Code

#define R_SYS_CLK_STATUS(Data)                PG(0).ReadBit(0x38, 1, 0, (uint8_t *)Data)  //   ST_MAIN_CLK

#define R_INT_STA_ACC_NM(Data)                PG(0).ReadBit(0x37, 1, 7, (uint8_t *)Data)  //   ACC_NM
#define R_INT_STA_ACC_AM(Data)                PG(0).ReadBit(0x37, 1, 6, (uint8_t *)Data)  //   ACC_AM
#define R_INT_STA_ACC_HIGH_G(Data)            PG(0).ReadBit(0x37, 1, 5, (uint8_t *)Data)  //   ACC_HIGH_G
#define R_INT_STA_GYR_HIGH_G(Data)            PG(0).ReadBit(0x37, 1, 3, (uint8_t *)Data)  //   ACC_HIGH_G
#define R_INT_STA_GYR_AM(Data)                PG(0).ReadBit(0x37, 1, 2, (uint8_t *)Data)  //   ACC_AM

#define R_ST_RESULT(Data)                     PG(0).ReadBit(0x36, 4, 3, (uint8_t *)Data)       //   Bit 0 = Acc, 1 = Mag,  2 = Gyro, 3 = MCU
#define R_ST_RESULT_ST_MCU(Data)              PG(0).ReadBit(0x36, 1, 3, (uint8_t *)Data)  //   ST_MCU
#define R_ST_RESULT_ST_GYR(Data)              PG(0).ReadBit(0x36, 1, 2, (uint8_t *)Data)  //   ST_GYR
#define R_ST_RESULT_ST_MAG(Data)              PG(0).ReadBit(0x36, 1, 1, (uint8_t *)Data)  //   ST_MAG
#define R_ST_RESULT_ST_ACC(Data)              PG(0).ReadBit(0x36, 1, 0, (uint8_t *)Data)  //   ST_ACC

#define R_CALIB_STAT(Data)                    PG(0).ReadByte(0x35,  (uint8_t *)Data)      //   Calibration Status 
#define R_SYS_CALIB_STAT(Data)                PG(0).ReadBit(0x35, 2, 7, (uint8_t *)Data)  //   SYS Calib Status 0:3
#define R_GYR_CALIB_STAT(Data)                PG(0).ReadBit(0x35, 2, 5, (uint8_t *)Data)  //   GYR Calib Status 0:3 
#define R_ACC_CALIB_STAT(Data)                PG(0).ReadBit(0x35, 2, 3, (uint8_t *)Data)  //   ACC Calib Status 0:3
#define R_MAG_CALIB_STAT(Data)                PG(0).ReadBit(0x35, 2, 1, (uint8_t *)Data)  //   MAG Calib Status 0:3

#define R_TEMP_Data(Data)                     PG(0).ReadByte(0x34, (uint8_t *)Data)       //  Temperature

#define R_GRV_Data(Data)                      PG(0).ReadInts(0x2E, 3, (int16_t *)Data)   //   Gravity Vector Data.
#define R_GRV_Data_X(Data)                    PG(0).ReadInt(0x2E, (int16_t *)Data)       //   Gravity Vector Data X <15:0>
#define R_GRV_Data_Y(Data)                    PG(0).ReadInt(0x30, (int16_t *)Data)       //   Gravity Vector Data Y <15:0>
#define R_GRV_Data_Z(Data)                    PG(0).ReadInt(0x32, (int16_t *)Data)       //   Gravity Vector Data Z <15:0>

#define R_LIA_Data(Data)                      PG(0).ReadInts(0x28, 3, (int16_t *)Data)   //   Linear Acceleration Data.
#define R_LIA_Data_X(Data)                    PG(0).ReadInt(0x28, (int16_t *)Data)       //   Linear Acceleration Data X <15:0>
#define R_LIA_Data_Y(Data)                    PG(0).ReadInt(0x2A, (int16_t *)Data)       //   Linear Acceleration Data Y <15:0>
#define R_LIA_Data_Z(Data)                    PG(0).ReadInt(0x2C, (int16_t *)Data)       //   Linear Acceleration Data Z <15:0>

#define R_QUA_Data(Data)                      PG(0).ReadInts(0x20, 4, (int16_t *)Data)   //   Quaternion Data.
#define R_QUA_Data_W(Data)                    PG(0).ReadInt(0x20, (int16_t *)Data)       //   Quaternion W Data <15:0>
#define R_QUA_Data_X(Data)                    PG(0).ReadInt(0x22, (int16_t *)Data)       //   Quaternion X Data <15:0>
#define R_QUA_Data_Y(Data)                    PG(0).ReadInt(0x24, (int16_t *)Data)       //   Quaternion Y Data <15:0>
#define R_QUA_Data_Z(Data)                    PG(0).ReadInt(0x26, (int16_t *)Data)       //   Quaternion Z Data <15:0>

#define R_EUL_Data(Data)                      PG(0).ReadInts(0x1A, 3, (int16_t *)Data)   //   Euler Heading Pitch and Roll.
#define R_EUL_Heading(Data)                   PG(0).ReadInt(0x1A, (int16_t *)Data)       //   Euler Heading Data < <15:0>
#define R_EUL_Roll(Data)                      PG(0).ReadInt(0x1C, (int16_t *)Data)       //   Euler Roll <15:0>
#define R_EUL_Pitch(Data)                     PG(0).ReadInt(0x1E, (int16_t *)Data)       //   Euler Pitch <15:0>

The following example will use the macros related to CALIB_STAT or the calibration state of the BNO055. the specific macro is: R_SYS_CALIB_STAT(Data)


//Example of its use:
Simple_BNO055 BNO;  // Note that Simple_BNO055 extends the Simple_Wire library
// ... other code of course...
uint8_t CalStatus;
/*
Because the macros represent the Read*** functions of the Simple_Wire library they will return a
 pointer to the library and not the value retrieved from the BNO055 IC. In order to use the value 
in a single line, I needed to have the last item in the class... return the value retrieved. I was 
hoping to make it as simple as possible For Example:
*/
if(BNO.R_SYS_CALIB_STAT(&CalStatus).Value(&CalStatus) == 3) {
   Serial.println("Calibration Complete");
} else {
   Serial.print("Calibration at level:");
   Serial.println(CalStatus);
}

//Alternatively, it is probably just as simple to do the following:
BNO.R_SYS_CALIB_STAT(&CalStatus);
if(CalStatus == 3) {
   Serial.println("Calibration Complete");
} else {
   Serial.print("Calibration at level:");
   Serial.println(CalStatus);
}

The option to do either is now available thanks to your help @J-M-L .

In Conclusion:
By using macros instead of a huge number of functions I have found it extremely simple to create a library that completely accesses every aspect of the i2c device. I have completed libraries for the MPU6050 which includes the MPU6500 and the MPU9*** series of i2c chips. I also in short order (about 3 days) created the library for the BNO055 i2c chip. The BNO055 is more expensive but far superior to the MPU6050 MPUs.

I've also resolved many of the issues I faced with compatibility between the Arduino Uno and the ESP32 processors I don't have any others to test but I can't see why minimal modifications wouldn't provide compatibility with them also.

Thanks, Everyone
Z

All the source code and examples can be found Here Simple_BNO055 and Here Simple_Wire

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.