Base class does not have a member named `...`

I have two legs up to their knees solidly in the C mud (:wink:) Every time that I try to pull a leg out to place it on the (possibly firmer) C++ mud, the other goes deeper in the C mud and I never get out of it.

I have a scenario where I thought that a base class plus some derived classes would provide a perfect solution. The base class contains a pointer to a c-string.

The first battle was won, the base class requires a default constructor. It seems to be the way C++ works but I really had something like .... I'm now with the one leg one centimeter less deep in the C mud.

The next battle was that I need an array of (different) derived classes; I think that I also won this one by using pointers (I understand why); and again that one leg one centimeter less deep in de C mud.

And another battle that I won was the pure virtual function. Great, the one leg 3 centimeters out of the C mud without sinking the other one deeper into it.

But now I need your help so that I don't sink the legs deeper in the C mud :wink: This is the code that I'm working on to get the understanding and the basics in place.

// base class for upload data
class DsmrData
{
  public:
    const char *data;

    DsmrData(const char *dta)
    {
      data = dta;

      Serial.print(F("UplDta constructor "));
      //printData();
    }

    // we need a default constructor :(
    // omitting will result in errors
    // why do derived classes call the default constructor; seems to be part of the language :(
    DsmrData()
    {
      Serial.print(F("Default DsmrData constructor "));
      //printData();
    }

    // needs to be a pure virtual function;
    virtual void convert() = 0;

    void printData()
    {
      Serial.print(F("'"));
      Serial.print(data);
      Serial.println(F("'"));
    }
};

// derived class for float data to be uploaded
class DsmrFloat : public DsmrData
{
  public:
    float x;
    DsmrFloat(const char *dta)
    {
      data = dta;
      Serial.print(F("DsmrFloat constructor "));
      printData();
    }

    void convert()
    {
      x = atof(data);
    }
};

// derived class for int32_t data to be uploaded
class DsmrInt32 : public DsmrData
{
  public:
    int32_t x;
    DsmrInt32(const char *dta)
    {
      data = dta;
      Serial.print(F("DmsrInt32 constructor "));
      printData();
    }
    void convert()
    {
      x = atoi(data);
    }
};

// derived class for literal data to be uploaded
class DsmrLiteral : public DsmrData
{
  public:
    char x[51];
    DsmrLiteral(const char *dta)
    {
      data = dta;
      Serial.print(F("DsmrLiteral constructor "));
      printData();
    }
    void convert()
    {
      strcpy(x, data);
    }
};

// derived class for text data to be uploaded
class DsmrText : public DsmrData
{
  public:
    char x[51];
    DsmrText(const char *dta)
    {
      data = dta;
      Serial.print(F("DsmrText constructor "));
      printData();
    }
    void convert()
    {
      strcpy(x, data);
    }
};

void setup()
{
  Serial.begin(115200);
  Serial.println(F("Hi there"));

  DsmrInt32 uploadInteger("123");
  DsmrFloat uploadFloat("3.14");
  DsmrLiteral uploadLiteral("Hello World!");

  DsmrData *ui = &uploadInteger;
  DsmrData *uf = &uploadFloat;
  DsmrData *ut = &uploadLiteral;

  DsmrData *dataToUpload2[] = {
    &uploadInteger,
    uf,
    ut,
  };

  for (uint8_t cnt = 0; cnt < (sizeof(dataToUpload2) / sizeof(dataToUpload2[0])); cnt++)
  {
    dataToUpload2[cnt]->printData();
    dataToUpload2[cnt]->convert();
    // error: 'DsmrData' has no member named 'x'
    Serial.println(dataToUpload2[cnt]->x);
  }
}

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

}

The error

DSMRwithClasses:138:40: error: 'class DsmrData' has no member named 'x'
     Serial.println(dataToUpload2[cnt]->x);
  1. Obviously it does not have a member variable x, in my view that is the whole purpose of the use of derived classes.
  2. I can obviously use some form of a cast but that would imply that either the code needs to know the type or I must cast manually (no idea how); it also defeats, in my view, the purpose of the derived classes, it should be 'automagic'.

Please advise what I'm missing and how I could solve this; I've googled this on-and-off for a a couple of days now and haven't been able to solve it :cry:

Thanks in advance to those that can help me not to get more firmly in the C mud :smiley:

As you see, if you have the member x only in a derived class, you can't access it from the base class.
Can you explain what you want to achieve? It seems inheritance is not the best solution in your case.

btw a Serial print in the constructor body is a bad idea, at least when you start to create objects in global scope.

You can't access member variables of child class from base class as you can do with virtual methods.
However in your case you can use virtual "getter" function. Declare it as pure virtual in base class:

virtual void print_x() = 0;

Than redefine in the child classes as

virtual void print_x() {Serial.println(x);}

and than use for your array:

for (uint8_t cnt = 0; cnt < (sizeof(dataToUpload2) / sizeof(dataToUpload2[0])); cnt++)
  {
    dataToUpload2[cnt]->printData();
    dataToUpload2[cnt]->convert();
   
    dataToUpload2[cnt]->print_x();
  }

Tested on Arduino IDE 1.8.13, compiled and works fine.

Thanks people,

I don't want to access it from the base class :slight_smile: I want to access it from the derived class :smiley: But I know that there is a lot about the inners of C++ / OOP that I don't know.

My application receives serial data ion Rx2 (Mega) at regular intervals (1 second or 10 seconds); example lower down. It basically consists of key/value pairs, one per line. As an example of the key/value pairs

key value(s) type
1-3:0.2.8 42 integer
1-0:1.8.1 123456.789*kWh float + text

So what I thought of doing is using a base class with a c-string member for the text "42" or "123456.789*kWh". And have derived classes that handle the conversion to a variable (called 'x') of the correct type (integer, float, ...); I can probably live without this and just make the convert() function return the integer or float (or ...) but I started with the variable 'x' so I have to do the conversion only once and now I want to continue with the variable 'x' to understand how it should be done.

I'm not sure if it requires more explanation.

Note that I have a functional application in place that does receive the serial data and parses and converts it to the correct type (for further processing) and sends the data for selected keys to serial monitor (and in future to the web); it does not use classes. I however want to optimise it a bit and thought of using a (array of) structs containing the key plus a function pointer (to the conversion function); but I have not quite figured out the right way to do it so I don't have to worry about the type of the converted data. And that is where I thought that a base class plus derived classed would be useful and hence I skipped that optimisation for now.

Example from https://www.netbeheernederland.nl/_upload/Files/Slimme_meter_15_32ffe3cc38.pdf (note that the CRC (last line) is incorrect)

/ISk5\2MT382-1000

1-3:0.2.8(42)
0-0:1.0.0(101209113020W)
0-0:96.1.1(4B384547303034303436333935353037)
1-0:1.8.1(123456.789*kWh)
1-0:1.8.2(123456.789*kWh)
1-0:2.8.1(123456.789*kWh)
1-0:2.8.2(123456.789*kWh)
0-0:96.14.0(0002)
1-0:1.7.0(01.193*kW)
1-0:2.7.0(00.000*kW)
0-0:96.7.21(00004)
0-0:96.7.9(00002)
1-0:99.97.0(2)(0-0:96.7.19)(101208152415W)(0000000240*s)(101208151004W)(0000000301*s)
1-0:32.32.0(00002)
1-0:52.32.0(00001)
1-0:72:32.0(00000)
1-0:32.36.0(00000)
1-0:52.36.0(00003)
1-0:72.36.0(00000)
0-0:96.13.1(3031203631203831)
0-0:96.13.0(303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F303132333435363738393A3B3C3D3E3F)
1-0:31.7.0.255(001*A)
1-0:51.7.0.255(002*A)
1-0:71.7.0.255(003*A)
1-0:21.7.0.255(01.111*kW)
1-0:41.7.0.255(02.222*kW)
1-0:61.7.0.255(03.333*kW)
1-0:22.7.0.255(04.444*kW)
1-0:42.7.0.255(05.555*kW)
1-0:62.7.0.255(06.666*kW)
0-1:24.1.0(003)
0-1:96.1.0(3232323241424344313233343536373839)
0-1:24.2.1(101209110000W)(12785.123*m3)
!CE7C

I'm aware of that; they will disappear once I understand all this. But it's the reason why the objects are constructed in setup() instead of global.

I will look at your solution later.

I'm still not convinced that inheritance is of any advantage for your usecase.

OT: I know that you are experienced enough to read your device, but if you want to get another idea, you can look at my smart meter implementation I did some months ago:

https://werner.rothschopf.net/microcontroller/202112_noiasca_smartmeter.htm

TLDR: You are accessing it from the derived-class in a manner which only sees the members of the base-class.

C++ will happily allow you to create a pointer of the base-class type, which is pointing to an derived-class object, as you have done here:

  DsmrData *ui = &uploadInteger;
  DsmrData *uf = &uploadFloat;
  DsmrData *ut = &uploadLiteral;

  DsmrData *dataToUpload2[] = { &uploadInteger, uf, ut, };

However through this pointer you can only access the elements in the base-class. When you try to access the objects using this pointer

  dataToUpload2[cnt]->x;

the compiler only looks at the definition of the DsmrData-class, not the derived classes, so it does not know that the objects actually do have member named 'x'.

b707's suggestion is probably the best way to make the code do what you want with the least amount of work :slight_smile:

A word of caution about constructors...

  public:
    const char *data;
...
    DsmrData()
    {
      Serial.print(F("Default DsmrData constructor "));
      //printData();
    }

Because you do not set data to a value, like NULL, in your default constructor, it is uninitialized after construction. data being used without being initialized...

    void convert()
    {
      x = atof(data);
    }

...is undefined behaviour. It's safe to assume that undefined behaviour will also be very undesirable behaviour.

A (arguably) convenient way to initialize fields is with a "member initializer list"...

    DsmrData() : data(NULL)
   {
...

In other words, by you defining a default constructor you are overriding the automatic zero initialization.

To expand on that, I found this piece of advice:

@b707
Thanks

The Serial.println(dataToUpload2[cnt]->x); was just an example trying to access the member x from the "outside" world. Maybe it's not possible, maybe it's not needed.

The biggest objection that I have against the approach (which is more than likely forced by C++/OOP) is that adding serial functions to the classes introduces a dependency on the Serial object which does not make sense to me. I was hoping that I just automagically could get the value with the correct type out and other code would recognise that it was an int or a float; I'll keep on dreaming :slight_smile:

Your example however has giving me the idea how I can probably approach my use case; instead of printing, use e.g. dtostrf or sprintf depending on the child class. But it's actually pretty useles that way as I feed a text in, store it as a float/int/... and next retrieve it as text again.

I did puzzle a little more and came up with something else as shown below; this does not store the converted value in the class but in variables outside the class.

// should go in (an array of) class or struct
// that's outside the scope of this forum topic
float mEVLT;
float mEVHT;
int dsmrVersion;


class DsmrBase
{
protected:
  char key[20];
  char description[20];

public:

  // TODO: figure out how to get rid of the "unused variable" warning
  DsmrBase(const char *k, const char *d, void *s)
  {
    // TODO: solve error "undefined reference to `vtable for DsmrBase'"
    //memset(key, '\0', sizeof(key));
    //memset(description, '\0', sizeof(description));
    strncpy(key, k, sizeof(key) - 1);
    key[sizeof(key) - 1] = '\0';
    strncpy(description, d, sizeof(description) - 1);
    // because memset does not work :-(
    description[sizeof(description) - 1] = '\0';
  }

  /*
    get the key
    Returns:
      pointer to key
  */
  char *getKey()
  {
    return key;
  }
  /*
    get the descriptive text
    Returns:
      pointer to descriptive text
  */
  char *getDescription()
  {
    return description;
  }

  // pure virtual functions; to be defined in derived classes
  virtual bool getValue(char *buffer, size_t size) = 0;
  virtual void parse(const char *data) = 0;
  virtual char *getUnits();
};

class DsmrFloat : public DsmrBase
{
private:
  float x;
  char units[10];
  float *storage;

public:
  DsmrFloat(const char *k, const char *d, float *s)
    : DsmrBase(k, d, s)
  {
    storage = s;
  }

  /*
    parse a float value of a DSMR record
    In:
      data to parse
  */
  void parse(const char *data)
  {
    Serial.print(__PRETTY_FUNCTION__);
    Serial.print(F(" parsing "));
    Serial.println(data);

    char *endptr;
    x = strtod(data, &endptr);
    if (endptr == data)
    {
      Serial.print(__PRETTY_FUNCTION__);
      Serial.println(F(" Error: data does not contain a float"));
    }
    else
    {
      if (storage != nullptr)
      {
        *storage = x;
      }
      if (*endptr == '*')
      {
        memset(units, '\0', sizeof(units));
        endptr++;
        strcpy(units, endptr);
      }
      else
      {
        strcpy(units, "-");
      }
    }
  }

  /*
    get the value
    In:
      buffer where to store
      size of buffer
    Returns:
      always true
  */
  bool getValue(char *buffer, size_t size)
  {
    dtostrf(x, -1, 3, buffer);
    return true;
  }

  /*
    get the units of the value
    Returns:
      (pointer to) units
  */
  char *getUnits()
  {
    return units;
  }
};

class DsmrInt : public DsmrBase
{
private:
  int x;
  char units[10];
  int *storage;

public:
  DsmrInt(const char *k, const char *d, int *s)
    : DsmrBase(k, d, s)
  {
    storage = s;
  }

  /*
    parse an interger value of a DSMR record
    In:
      data to parse
  */
  void parse(const char *data)
  {
    Serial.print(__PRETTY_FUNCTION__);
    Serial.print(F(" parsing "));
    Serial.println(data);

    char *endptr;
    x = strtol(data, &endptr, 10);
    if (endptr == data)
    {
      Serial.print(__PRETTY_FUNCTION__);
      Serial.println(F(" Error: data does not contain an integer"));
    }
    else
    {
      if (storage != nullptr)
      {
        *storage = x;
      }

      if (*endptr == '*')
      {
        memset(units, '\0', sizeof(units));
        endptr++;
        strncpy(units, endptr, sizeof(units) - 1);
      }
      else
      {
        strcpy(units, "-");
      }
    }
  }

  /*
    get the value
    In:
      buffer where to store
      size of buffer
    Returns:
      always true
  */
  bool getValue(char *buffer, size_t size)
  {
    snprintf(buffer, size - 1, "%d", x);
    return true;
  }

  /*
    get the units of the value
    Returns:
      (pointer to) units
  */
  char *getUnits()
  {
    return units;
  }
};


DsmrBase *dsmrRecords[] = {
  (DsmrBase *)new DsmrFloat("1-0:1.8.1", "delivered (tariff 1)", &mEVLT),
  (DsmrBase *)new DsmrFloat("1-0:1.8.2", "delivered (tariff 2)", &mEVHT),
  (DsmrBase *)new DsmrInt("1-3:0.2.8", "DSMR version", &dsmrVersion),
};

void setup()
{
  char buffer[64];
  Serial.begin(115200);


  dsmrRecords[0]->parse("1234.567*kWh");
  dsmrRecords[1]->parse("112233.456*kWh");
  dsmrRecords[2]->parse("42");

  for (uint16_t cnt = 0; cnt < sizeof(dsmrRecords) / sizeof(dsmrRecords[0]); cnt++)
  {
    Serial.println(dsmrRecords[cnt]->getKey());
    Serial.println(dsmrRecords[cnt]->getDescription());
    // the useless function ;-)
    dsmrRecords[cnt]->getValue(buffer, sizeof(buffer));
    Serial.println(buffer);
    Serial.println(dsmrRecords[cnt]->getUnits());
  }

  Serial.println(F("===="));
  Serial.println(mEVLT);
  Serial.println(mEVHT);
  Serial.println(dsmrVersion);
}

void loop()
{
}

Comments on this approach / solution by everybody are very welcome. Note that I left the useless function in :wink:

There are two TODO comments in the base class; if somebody can shine some light on it, that would be appreciated.

Thanks; the only reason for the default constructor was that the compiler complained (can't remember exactly how) and I had not figured out yet how to get rid of the need for it. As you can see in the latest code, I think that I did find the solution for that problem. The only issue is that I get a warning on the base class the the 3rd parameter is not used :frown: I need to find a way around that (learning/studying again).

Thanks; I will (one day :wink:) read through all of it. Looks that it addresses a lot of gotcha's; some of it might be over my head though. But learning :wink:

@noiasca

I had a quick look at your .h file. The parseLine function looked very familiar. This project started with a friend having a problem reading the data from the smart meter. So I said the I could have a look at his program; he used a similar way of reading lines, different approach to the extracting of the values. He also combined it with a lot of analogue reads and he submits to a webserver.

And I started writing from scratch; the only specs that I actually have is the one linked in post #4 as well as the one for DSMR4. I did try to find other information about the length of values in the records etc but did not manage; you got a lot further with that :+1: I do not have a meter so I had to write a simulator (running on a Leonardo) that can pump out the data every 10 seconds (DSMR4) or every second (DSMR5).

I did base my reader as well on Robin's tutorial, however example 3 (recvWithStartEndMarkers()) and I read a full telegram from / to ! plus the crc. It's however far more hardened than Robin's example with timeouts, catching data overflows and crc overflows and it includes crc checking. The example data in post #4 is nearly 1100 bytes and that was what I did set the size of the receivebuffer to. I can afford to store 1100 bytes in memory because he is using an Arduino Due; I'm using a Mega. I understand your approach as it will run on processors with less RAM like Uno or Leonardo.

Once I have the telegram, I parse it for the fields of interest. At this moment I use a similar approach to yours for that and that was actually why I started this topic; I felt that my code had too much repeating code :wink:

From the above oost #9

DsmrBase *dsmrRecords[] = {
  (DsmrBase *)new DsmrFloat("1-0:1.8.1", "delivered (tariff 1)", &mEVLT),
  (DsmrBase *)new DsmrFloat("1-0:1.8.2", "delivered (tariff 2)", &mEVHT),
  (DsmrBase *)new DsmrInt("1-3:0.2.8", "DSMR version", &dsmrVersion),
};

My intention is that dsmrRecords contains all records of interest (the example only shows 3). Next the code can loop through that, extract the correct data (e.g. for "1.0:1.8.1") from the 1100 bytes and store it in a relevant variable for further processing. That will get rid of all repeating code.

The additional advantage is that this will make it possible to have a dedicated include file where he can add/remove records and fields for the OBIS code of interest and he does not have to dig through thousands of lines of code with the risk that he damages the main code.

PS
His meter is nasty; it should pump out N bytes in no time; instead it adds a 30 ms delay after each record :frowning:

yes, the parser is very close Robins tutorial. I find it very stable and reliable.

I understand you ^^. Yes, I dislike these 10 - 15 segments of duplicated code in my sketch also.
But let's be honest, how flexible should a parser be? In the end someone has a smartmeter and that smartmeter will send a specified set of tags.

However, if I would do it again, I would try an array of struct, with 2 or 3 members: tag, pointer to a variable and text (so more or less your parameters of the constructor) and handover that array to the parser.

I've marked my post #9 as solution because it comes (for now) closest to what I hope to achieve.

Thanks again for all the input :+1:

It would be cleaner with C++17 as that would give access to std::variant. But, this is a crude roll your own version. Compiles but is untested.

class BaseClass {
  public:
    struct VariantType {
      enum DataType {U32_TYPE, I32_TYPE, FLOAT_TYPE};
      DataType dataType;
      union {
        uint32_t u32;
        int32_t i32;
        float fl;
      };

      VariantType(uint32_t u) {
        dataType = U32_TYPE;
        u32 = u;
      }

      VariantType(int32_t i) {
        dataType = I32_TYPE;
        i32 = i;
      }

      VariantType(float f) {
        dataType = FLOAT_TYPE;
        i32 = f;
      }
    };
    virtual VariantType getValue() = 0;
};

class DerivedU32 : public BaseClass {
  public:
    DerivedU32(uint32_t u) {
      value = u;
    }

    virtual VariantType getValue() {
      VariantType variant {value};
      return variant;
    }

  private:
    uint32_t value;
};

class DerivedI32 : public BaseClass {
  public:
    DerivedI32(uint32_t i) {
      value = i;
    }

    virtual VariantType getValue() {
      VariantType variant {value};
      return variant;
    }

  private:
    int32_t value;
};

class DerivedFloat : public BaseClass {
  public:
    DerivedFloat(float f) {
      value = f;
    }

    virtual VariantType getValue() {
      VariantType variant {value};
      return variant;
    }

  private:
    float value;
};

BaseClass *baseArray[] = {
  new DerivedU32(1000),
  new DerivedI32(-5000),
  new DerivedFloat(3.14159)
};

void setup() {
  Serial.begin(115200);

  for (auto p : baseArray) {
    BaseClass::VariantType varient = p->getValue();
    switch (varient.dataType) {
      case BaseClass::VariantType::U32_TYPE:
        Serial.println(varient.u32);
        break;

      case BaseClass::VariantType::I32_TYPE:
        Serial.println(varient.i32);
        break;

      case BaseClass::VariantType::FLOAT_TYPE:
        Serial.println(varient.fl);
        break;

      default:
        break;
    }
  }
}

void loop() {
}

You could probably also wrap all those derived class definitions into a template.

Using a simple template. Again, it compiles but is untested.

class BaseClass {
  public:
    struct VariantType {
      enum DataType {U32_TYPE, I32_TYPE, FLOAT_TYPE};
      DataType dataType;
      union {
        uint32_t u32;
        int32_t i32;
        float fl;
      };

      VariantType(uint32_t u) {
        dataType = U32_TYPE;
        u32 = u;
      }

      VariantType(int32_t i) {
        dataType = I32_TYPE;
        i32 = i;
      }

      VariantType(float f) {
        dataType = FLOAT_TYPE;
        i32 = f;
      }
    };
    virtual VariantType getValue() = 0;
};

template<class T>
class TemplateDerived : public BaseClass {
  public:
    TemplateDerived(T v) : value(v) {
    }

    virtual VariantType getValue() {
      VariantType variant {value};
      return variant;
    }

  private:
    T value;

};

BaseClass *baseArray[] = {
  new TemplateDerived<uint32_t>(1000),
  new TemplateDerived<int32_t>(-5000),
  new TemplateDerived<float>(3.14159)
};

void setup() {
  Serial.begin(115200);

  for (auto p : baseArray) {
    BaseClass::VariantType varient = p->getValue();
    switch (varient.dataType) {
      case BaseClass::VariantType::U32_TYPE:
        Serial.println(varient.u32);
        break;

      case BaseClass::VariantType::I32_TYPE:
        Serial.println(varient.i32);
        break;

      case BaseClass::VariantType::FLOAT_TYPE:
        Serial.println(varient.fl);
        break;

      default:
        break;
    }
  }
}

void loop() {
}

Fixed a minor error. Tested and working.

class BaseClass {
  public:
    struct VariantType {
      enum DataType {U32_TYPE, I32_TYPE, FLOAT_TYPE};
      DataType dataType;
      union {
        uint32_t u32;
        int32_t i32;
        float fl;
      };

      VariantType(uint32_t u) {
        dataType = U32_TYPE;
        u32 = u;
      }

      VariantType(int32_t i) {
        dataType = I32_TYPE;
        i32 = i;
      }

      VariantType(float f) {
        dataType = FLOAT_TYPE;
        fl = f;
      }
    };
    virtual VariantType getValue() = 0;
};

template<class T>
class TemplateDerived : public BaseClass {
  public:
    TemplateDerived(T v) : value(v) {
    }

    virtual VariantType getValue() {
      VariantType variant {value};
      return variant;
    }

  private:
    T value;
};

BaseClass *baseArray[] = {
  new TemplateDerived<uint32_t>(1000),
  new TemplateDerived<int32_t>(-5000),
  new TemplateDerived<float>(3.14159)
};

void setup() {
  Serial.begin(115200);
  delay(1000);

  for (auto p : baseArray) {
    BaseClass::VariantType varient = p->getValue();
    switch (varient.dataType) {
      case BaseClass::VariantType::U32_TYPE:
        Serial.println(varient.u32);
        break;

      case BaseClass::VariantType::I32_TYPE:
        Serial.println(varient.i32);
        break;

      case BaseClass::VariantType::FLOAT_TYPE:
        Serial.println(varient.fl);
        break;

      default:
        break;
    }
  }
}

void loop() {
}

@gfvalvo

Thanks for your suggestions. When I have time I will try to understand and try to apply.