class instance constructor for undetermined number of array elements

continuing to up my game with OOP and Classes...

I have reduced my code down to just a few lines to ask this question. I am creating a class where I will need to have constructors which will allow the last variable to have an undetermined number of elements in a char array. I know that I can create multiple constructor formats for this, but can I create a single constructor format which will allow the last variable to have say anywhere from 1 to 6 elements?

Here I show how I am doing it by having multiple constructor formats for either one or two instances of values for m_delimiter. Is there a way I can do this with only one constructor format for an unknown number of m_delimiter values?

I imagine there is an easy way, but so far, my research on this has eluded me.
THANK YOU for your advice on this.

class Messages {
  private:
    char       m_startChar;
    char       m_delimiters[];

  public:
    // constructor format  for single delimiter instance
    Messages(char SC, char DL1)
    {
      m_startChar     = SC;
      m_delimiters[0] = DL1;
    }

    // constructor format for two delimter instance
    Messages(char SC, char DL1, char DL2)
    {
      m_startChar     = SC;
      m_delimiters[0] = DL1;
      m_delimiters[1] = DL2;
    }
 };

Try providing default values for the parameters. Then, your constructor can examine all of them and determine which ones the calling code actually specified.

You could pass the delimiters as an array:

class Messages {
  private:
    char startChar;
    char delimiters[6] = {};

  public:
    Messages(char startChar, const char (&delimiters)[6])
      : startChar(startChar) {
      memcpy(this->delimiters, delimiters, sizeof(delimiters));
    }

    void print() const {
      Serial.println("Start:"), Serial.println(startChar);
      Serial.println("Delimiters:");
      for (char dl : delimiters) {
        if (dl) Serial.println(dl);
      }
    }
};

void setup() {
  Serial.begin(115200);
  while (!Serial);
  Messages m = {'s', {'a', 'b', 'c'}};
  m.print();
}

void loop() {}

You can't initialize arrays in an initializer list, so you'll have to copy them manually, and it always copies all 6 elements, but other than that, I think it's a good solution.

You could use a template to copy only the elements you passed, and print a nice error message when the user supplies too many delimiters:

   template <size_t NumDelimiters>
    Messages(char startChar, const char (&delimiters)[NumDelimiters])
      : startChar(startChar) {
      static_assert(NumDelimiters <= sizeof(this->delimiters), "Too many delimiters");
      memcpy(this->delimiters, delimiters, sizeof(delimiters));
    }

Pieter

PieterP, Thank you for the options. I am challenged to understand the code, but thank you nonetheless.

As I understand your question.. I'd try something like this..

In the .h class definition..

mrConstruct(char* inStr1=NULL, char* inSter2=NULL, char* inStr3=NULL);

In the .cpp code bit.

myClass::mrConstruct(char* inStr1, char* inSter2, char* inStr3) {

if (inStr1) { deal with first string }
if (inStr2) { deal with second string }
if (instr3) {deal with third. }
}

Created by program..

myClass newObj = new myClass("hello baby!");
myClass newObj2 = new myClass("hello baby!", "What's up?");
myClass newObj3 = new myClass("Don't you be", "Looking","At me!");

Now.. don't go trying to change the strings in your constructor. But you will now know how long they are and can make copies.

Or I missed the point completly

-jim lee

jimLee:
mrConstruct(char* inStr1=NULL, char* inSter2=NULL, char* inStr3=NULL);

String literals should always be stored as read-only pointers (const char *).
Additionally, the class shouldn't write to any of these strings, so leaving out the const would result in a misleading API, making the use of your library impossible when combined with code that does follow the const guidelines correctly.

Use of NULL in C++ code is discouraged, because NULL might be defined as the integer 0, which could lead to hard to spot bugs. You want nullptr instead, so there's no ambiguity between NULL as an integer NULL as a character, or NULL as a pointer.

mrConstruct(const char *inStr1 = nullptr, const char *inSter2 = nullptr, const char *inStr3 = nullptr);

jimLee:
Created by program..

myClass newObj1 = new myClass("hello baby!");
myClass newObj2 = new myClass("hello baby!", "What's up?");
myClass newObj3 = new myClass("Don't you be", "Looking","At me!");

That won't compile: the output of new myClass(...) is myClass * (pointer to myClass), you can't assign that to a variable of type myClass.
Even if you fixed that, you still shouldn't be using new, it causes your program to leak memory if you don't take care of delete-ing them afterwards.

There's no reason to use dynamic memory here, simply use automatic storage on the stack:

myClass newObj = { "hello baby!" };
myClass newObj2 = { "hello baby!", "What's up?" };
myClass newObj3 = { "Don't you be", "Looking","At me!" };

jimLee:
As I understand your question.. I'd try something like this..

In the .h class definition..

mrConstruct(char* inStr1=NULL, char* inSter2=NULL, char* inStr3=NULL);

In the .cpp code bit.

myClass::mrConstruct(char* inStr1, char* inSter2, char* inStr3) {

if (inStr1) { deal with first string }
if (inStr2) { deal with second string }
if (instr3) {deal with third. }
}

Jim,
That is a solution but no simpler than what I was already trying with multiple constructors. The thing is also, that having the delimiters in an array, the way I have it, allows me to use strtok and specify all the delimiters as just the name of the array.

petedd:
PieterP, Thank you for the options. I am challenged to understand the code, but thank you nonetheless.

What parts are you having trouble with exactly?

const char (&delimiters)[6] is a read-only reference to an array of 6 characters. The array is passed by reference, because C-style arrays behave quite differently from normal objects, you can't really pass them by value, if you try to do that, they decay to a pointer to the first element, and the code won't work as expected.

: startChar(startChar) is a member initializer list, it initializes the member variable "startChar" with the value of the parameter "startChar".

memcpy(this->delimiters, delimiters, sizeof(delimiters)); copies the contents of the parameter "delimiters" to the member variable "delimiters". The number of bytes copied here is sizeof(delimiters).
It's fine for simple things like arrays of characters, but be very careful when using memcpy for more complex objects, it copies the underlying bit pattern (the raw bytes), it doesn't perform a clean "deep copy" and doesn't call the appropriate copy assignment operators.

PieterP:
String literals should always be stored as read-only pointers (const char *).
Additionally, the class shouldn't write to any of these strings, so leaving out the const would result in a misleading API, making the use of your library impossible when combined with code that does follow the const guidelines correctly.

Thank you again Pieter.

I have used const char * for my strings in classes before and was able to change the content of the string, as a complete string literal - yes, taking advantage of the non-strict enforcement of const on the Arduino compiler.

But what I want to do is to hold a string as a character array in the class and manipulate that character array in the class methods. How does one accomplish that? That is my real question.

petedd:
I have used const char * for my strings in classes before and was able to change the content of the string, as a complete string literal - yes, taking advantage of the non-strict enforcement of const on the Arduino compiler.

You're not allowed to write to string literals. Sometimes, it might work if you're (un)lucky, other times, it'll produce meaningless results. The compiler warns you about these kinds of things for a reason.

By using const, you promise to the users of your class that you won't alter the strings they give you. If you cast away the "const" and modify them anyway, you break that promise. Regardless of whether it's allowed by the C++ standard or not, and you end up with an inconsistent API.

petedd:
But what I want to do is to hold a string as a character array in the class and manipulate that character array in the class methods. How does one accomplish that? That is my real question.

The code I posted in reply #2 stores the delimiters as a mutable array of 6 characters. You are allowed to modify that array.

(You are not allowed to write to the constructor parameter "delimiters", only to the member variable "delimiters".)

Thanks Pieter. I got it. I am though suffering another issue with char arrays/strings...

https://forum.arduino.cc/?topic=704959#msg4737709

care to take a shot at that one?

petedd:
Jim,
That is a solution but no simpler than what I was already trying with multiple constructors. The thing is also, that having the delimiters in an array, the way I have it, allows me to use strtok and specify all the delimiters as just the name of the array.

No worries, I was just showing you how I'd go at it. Never really understood the question anyway.

-jim lee