Is it possible to prevent implicit typecasts for a specific function?

I have an implementation of the standard C++ ostream class, and I need operator<< to print all fundamental data types. I need to be very conscious about memory usage. I have run into an issue: I cannot print a char as an actual character. Instead, it gets converted into an int. If I define a function to handle chars, other datatypes (like long) give me an “Ambiguous Overload” error. Is there a way to prevent type conversion in the char function, but allow it in the int function? Also, I cannot define functions for every data type to prevent conversion, since that would take up a lot more program space. I realize that I could just avoid using chars altogether, but that would mean I had to use null-terminated strings for everything.

com.h: OStream class snippet

uint8_t format = DEC;
friend OStream& operator<< (OStream& out, int input);
friend OStream& operator<< (OStream& out, char input); //Prevent casting chars to ints
friend OStream& operator<< (OStream& out, const String& input);
friend OStream& operator<< (OStream& out, const __FlashStringHelper *input);
friend OStream& operator<< (OStream& out, const CoordPair& coordPair);
friend OStream& operator<< (OStream& out, const ExactCoordPair& coordPair);
friend OStream& operator<< (OStream& out, Error error);
friend OStream& operator<< (OStream& out, IOStreamFlag flag);

com.cpp snippet:

OStream& operator<< (OStream& out, int input) {
  Serial.print(input, cout.format);
  return out;
}
OStream& operator<< (OStream& out, char input) { //This one is the issue
  Serial.print(input);
  return out;
}
OStream& operator<< (OStream& out, const String& input) {
  Serial.print(input);
  return out;
}
OStream& operator<< (OStream& out, const __FlashStringHelper *input) {
  Serial.print(input);
  return out;
}
//...other functions

If it ever got the point where I needed to worry about memory usage to that level of minutia, my first move would be to a processor with more resources. Unless you’re banging out thousands of units per month, hardware is cheap. Save yourself the aggravation. Besides, if you’re that close to the edge some other memory-related problem is bound to turn up later, even if you solve this one.

My instinct is that if the memory constraints are very tight, then C++ is not the right approach and you should stick to vanilla C where the exact memory footprint is more closely manageable.

But a couple of observations: having multiple flavours of method signatures does not necessarily bloat the code footprint since only method signatures (ie functions) that are actually used will ever be linked. It is common for functions or methods that need a “character” to accept an int type. Promotion of an int8_t, or uint8_t to an int16_t is really not a heavy price to pay. Typically such function arguments would fit in registers and the benefit of restricting a single character argument to an int8_t where an int16_t signature is also plausible, is likely specious.

I have no clue what you are thinking regarding “null-terminated strings for everything.” A char is an int8_t which is a scalar integer type. There is no obligation that a function or method that takes a char needs to handle a char*.

char input); //Prevent casting chars to ints

My question is “so what?” what is wrong with just letting the input be an int type? If it only needs to be a char/int8_t then make THAT be the method signature.

Thank you.

having multiple flavours of method signatures does not necessarily bloat the code footprint since only method signatures (i.e. functions) that are actually used will ever be linked.

I didn’t realize that, so I guess that solves the problem.

Promotion of an int8_t, or uint8_t to an int16_t is really not a heavy price to pay.

I need chars to be handled differently from other types because they need to be Serial.printed as characters, not numbers (e.g. cout << 'H';).

I have no clue what you are thinking regarding “null-terminated strings for everything.”

If I can’t handle chars differently from ints, then I would need to use a 1-character string (2 bytes) instead if a single char (1 byte).

It’s not about keeping the memory below a certain level; it’s about using as little memory as possible. Nearly every remaining byte of 8kiB is used for another pool of data, and I want that to be as large as possible.

I understand that if I wasn’t trying to design a project with specific restrictions, I could just buy more hardware.

Do an explicit conversion to the type you want when you call the function.

oStream << (int)12;
oStream << (int)millis();

*Facepalms* Why didn’t I think of that? Thank you.