Is there a way to have a trimmed() function for String?

I undertstand the trim() function change the String in place:

The trim() function modifies the String in place rather than returning a new one.

Sometimes this is very annoying, i.e. when you just need a trimmed version to be displayed but you don't want to mess with the original String. Example:

String s = "My string";
// ...
String display_s = s;
display_s.trim();
my_show_func(display_s)

if you have a lot of this stuff is more handy to use a trimmed() function:

String s = "My string";
my_show_func(s.trimmed());

if Arduino does not provide this function, is it possible to "add" it in some way so it will be enabled for the String variables in my code?

To be clear, of course I can just write something like:

String trimmed(String s)
{
  s.trim();
  return s;
}

but I wonder if using the magic of C++ is possible to add this function to the String class.

Just make a copy. I'm not sure why one wants to keep newlines, spaces etc in an original (the ones that you want to trim).

#define ArraySize sizeof(Array)/sizeof(Array[0])

is annoying too, i want its own class for this. :wink:

@kolaha, wrong topic? Or am I missing something?

not to the String class itself unless you modify the library

Not to original String class, but you could inherit your own class from the String and add the function to it

basic question:
what gets really trimmed in this example?

no leading whitespace, no trailing whitespace.
So there is nothing to be trimmed off

can you please give an example? I don't get the operator overloads working in the inherited class (or the constructor with assignment)


class MyString : public String {
  public:
    String getClean() {
      String clean = (*this);
      clean.trim();
      return clean;
    }
};

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

  MyString s;
  Serial.println(s.getClean());  // 

  //s = " My string "; // error: no match for 'operator='
  Serial.println(s.getClean());
}

void loop() {
}

sketch.ino:17:7: error: no match for 'operator=' (operand types are 'MyString' and 'const char [12]')
s = " My string "; // error: no match for 'operator='
^~~~~~~~~~~~~

It's just a placeholder for the code snippet!
Of course is it not the real string (that I don't know in advance anyway).

This error is a surprise for me too. Very interesting problem, but I don't know the answer yet.
Probably methods are inherited in some special way

indeed.

found that comment:
*With the exception of the assignment function operator =( ), all overloaded operator functions for class X are inherited by classes derived from X, *

https://docwiki.embarcadero.com/RADStudio/Athens/en/Overloaded_Operators_And_Inheritance

just a copy/paste from String.cpp

class MyString : public String {
  public:
    String getClean() {
      String clean = (*this);
      clean.trim();
      return clean;
    }


    MyString & operator = (const char *cstr) {
      if (cstr) copy(cstr, strlen(cstr));
      else invalidate();

      return *this;
    }
    // other = operator overloads need to be added
};

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

  MyString s;
  Serial.println(s.getClean());  //

  s = " My string "; // now ok (only that type of assignement!)
  Serial.println(s.getClean());

  s +=" test ";
  Serial.println(s.getClean());
}

void loop() {
}

may be a with a further hack to call the String::String & operator =
to avoid the copied code?

Can you read and display only the chars needed directly from the stored text? IIRC you can but it's a pain and you're not supposed to! You're supposed to waste RAM on a copy, like you have unlimited RAM.

Question is Why Use C++ Strings in a small RAM environment?
Ibet the answer has to do with not knowing better and conveniences!

So another alternative is to use the SafeString-library

SafeStrings offer almost the same comfort like Strings but the name is program
They are (at least more) safe to use than Strings

Here two WokSims as demo-codes

https://wokwi.com/projects/406450225342633985

/*
  SafeSting length(), trim()

  by Matthew Ford
  Copyright(c)2020 Forward Computing and Control Pty. Ltd.
  This example code is in the public domain.

  www.forward.com.au/pfod/ArduinoProgramming/SafeString/index.html
*/
#include "SafeString.h"


void setup() {
  // Open serial communications and wait a few seconds
  Serial.begin(115200);

  Serial.println();
  Serial.println(F("SafeString length(), trim() functions"));
  Serial.println(F("SafeString::setOutput(Serial); // verbose == true"));
  // see the SafeString_ConstructorAndDebugging example for debugging settings
  SafeString::setOutput(Serial); // enable full debugging error msgs
  Serial.println();

  // here's a String with empty spaces at the end (called white space):
  createSafeString(stringOne, 32, "  Hello!       ");
  createSafeString(stringTwo, 32);
  stringOne.debug();

  Serial.print("#");
  Serial.print(stringOne);
  Serial.println("#");
  Serial.print(F("stringOne.length() = ")); 
  Serial.println(stringOne.length());
  Serial.println();
  stringTwo = stringOne;
  stringTwo.trim();
  stringTwo.debug(F("stringTwo.trim(); => "));
  Serial.println("stringTwo.trim() done");
  Serial.print("#");
  Serial.print(stringTwo);
  Serial.println("#");
  Serial.print(F("stringTwo.length() = ")); 
  Serial.println(stringTwo.length());
  Serial.println();

  Serial.println(F(" c_str() gives to access to the underly char array as a const char* so you cannot change the contents"));
  Serial.println();

  Serial.print(F("stringTwo.c_str() = #")); 
  Serial.print(stringTwo.c_str());
  Serial.println("#");
  Serial.println();

  Serial.println();

}

void loop() {
}
#include <time.h>                   // time() ctime()
time_t now;                         // this is the epoch
tm myTimeInfo;                      // the structure tm holds time information in a more convient way


#include <SafeString.h>
createSafeString(myDemo_SS, 32);
createSafeString(mySecondDemo_SS, 32);
#define MaxTimeStampLength 64
createSafeString(TimeStamp_SS, MaxTimeStampLength);

createSafeString(myTitle_SS, 64);  // reserve 64 bytes for the SafeString-variable
createSafeString(myString_SS, 32); // reserve 32 bytes for the SafeString-variable

int myInteger = -1234;
float myFloat = -987.009;

unsigned long myCounter;


//useful function for non-blocking timing
boolean TimePeriodIsOver (unsigned long &periodStartTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();  
  if ( currentMillis - periodStartTime >= TimePeriod )
  {
    periodStartTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  } 
  else return false;            // not expired
}

unsigned long MyTestTimer = 0;                   // variables MUST be of type unsigned long
const byte    OnBoard_LED = 13; // Arduino-Uno Onboard-LED is IO-pin 13



void setup() {
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  myCounter = 0;
  myDemo_SS = "Hello world!";

  myString_SS = F("fixed text directly assigned");
  Serial.print( F(" #") ); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println(F("#") ); // trailing double-cross "#" to show where the string REALLY ends


  myTitle_SS = F("content of an integer:");
  myString_SS = myInteger;

  Serial.println(myTitle_SS);
  Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends



  myTitle_SS = F("content of a float:");
  myString_SS = myFloat;

  Serial.println(myTitle_SS);
  Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends

  myTitle_SS = F("you can append more text with the +=-operator ");
  myString_SS = F("text ");
  myString_SS += myInteger;
  myString_SS += F(" ,");
  myString_SS += myFloat;

  Serial.println(myTitle_SS);
  Serial.print( F(" #" )); // leading double-cross "#" to show where the string starts
  Serial.print(myString_SS); 
  Serial.println( F("#") ); // trailing double-cross "#" to show where the string REALLY ends  

}


void loop() {
  myCounter++;
  
  // loop is running very fast counting up very fast
  // but only once every 1234 milliseconds print 
  if ( TimePeriodIsOver(MyTestTimer,5234) ) {
    mySecondDemo_SS = myDemo_SS;  // assigning a SafeString to another SafeString
    
    mySecondDemo_SS += " ";       // append a SPACE
    mySecondDemo_SS += myCounter; // append integer-number
    Serial.println(mySecondDemo_SS);   
    StoreTimeStampIntoSS(TimeStamp_SS);
    Serial.println(TimeStamp_SS);
    Serial.println(TimeStamp_SS.c_str() );     
  }  

}


void StoreTimeStampIntoSS(SafeString& p_RefToSS) {

  time(&now);                               // read the current time
  localtime_r(&now, &myTimeInfo);           // update the structure tm with the current time

  //p_RefToSS = " ";
  p_RefToSS  = myTimeInfo.tm_year + 1900;
  p_RefToSS += ".";
  if (myTimeInfo.tm_mon + 1 < 10) {
    p_RefToSS += "0";  
  }
  p_RefToSS += myTimeInfo.tm_mon + 1;
  p_RefToSS += ".";

  if (myTimeInfo.tm_mday < 10) {
    p_RefToSS += "0";  
  }
  p_RefToSS += myTimeInfo.tm_mday;

  p_RefToSS += "; ";

  if (myTimeInfo.tm_hour < 10) {
    p_RefToSS += "0";  
  }  
  p_RefToSS += myTimeInfo.tm_hour;
  p_RefToSS += ":";

  if (myTimeInfo.tm_min < 10) {
    p_RefToSS += "0";  
  }  
  p_RefToSS += myTimeInfo.tm_min;
  p_RefToSS += ":";

  if (myTimeInfo.tm_sec < 10) {
    p_RefToSS += "0";  
  }  
  p_RefToSS += myTimeInfo.tm_sec;
}

Stefan - that's not the ask

you can do the exact same thing with the String class without the inflated SafeString library and without the constraint of a fixed max length.

@Mark81 wanted to know how do do it with the class.

@StefanL38
@GoForSmoke
The question is not about using a String or not, but about inheritance. Why the "=" operator, defined in parent class, doesn't available in the child class?

I would do

class CustomString : public String {
  public:
    using String::String;  // Inherit constructors from the String class

    CustomString trimCopy() const {
        CustomString trimmedStr(*this);
        trimmedStr.trim();
        return trimmedStr;
    }
};

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

  CustomString original = "   Hello, World!   \r\n    ";
  CustomString trimmed = original.trimCopy();

  Serial.print("Original: '");
  Serial.print(original);
  Serial.println("'");

  Serial.print("Trimmed: '");
  Serial.print(trimmed);
  Serial.println("'");
}

void loop() {}

Thank you all guys, it's an interesting thread. Naively, I thought we can do something like Javascript:

String.prototype.new_function = function () {
  // ...
};

mystring.new_function()

Without creating a new class. Instead I understood it's not possible in C++ unless you define your own class that inherits from String.

In Python or javascript, you can achieve the functionality by defining indeed a method and then attaching it to a class or an instance dynamically (unless it's passed) because you have a runtime.

C++ is statically compiled, you can't achieve the same thing.

"using" does the trick. Not only for the constructor but also for the = operators.
Thank you.
Learn something new every day.

class MyString : public String {
  public:
    using String::String;  // Inherit constructors from the String class and = operator overload
    MyString getClean() {
      MyString clean = (*this);
      clean.trim();
      return clean;
    }
};

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

  MyString s;
  Serial.println(s.getClean());  //

  s = " My string "; // now ok
  Serial.println(s.getClean());

  s +=" test ";
  Serial.println(s.getClean());
}

void loop() {
}

Imho the learning is that it can be done and it doesn't seem to generate a lot more source code compared with JS