0

I'm trying to overload some functions to work with several data types. This is trivial when I'm passing variables as parameters, thusly:

void fnc(int32_t d){
  Serial.println((String) "int32 = " + d);
}
void fnc(char* d){
    Serial.println((String) "string = " + d);
}

void fnc(bool d){
  if(d){ Serial.println((String) "bool = " + "true"); }
  else{ Serial.println((String) "bool = " + "false"); }
}

void fnc(float d){
  Serial.println((String) "float = " + d);
}

int32_t anInt = 29339222;
char* aStr = "baloney";
bool aBool = false;
float aFloat = 3.1415;

void setup() {
  Serial.begin(9600);
  fnc(anInt);
  fnc(aStr);
  fnc(aBool);
  fnc(aFloat);
}

(Note, this is on Arduino, hence setup() instead of main().)

Results in, as you would expect:

int32 = 29339222

string = baloney

bool = false

float = 3.14

Now then, I need to pass literal values as well as variables, like so:

  fnc(8728787);
  fnc("spamoney");
  fnc(true);
  fnc(2.718);

The compiler complains about the last line calling fnc(2.718):

sketch_apr1a.ino:34:12: error: call of overloaded 'fnc(double)' is ambiguous
   fnc(2.718);

I imagine that this is because as a literal, the parameter provides the compiler with no type information. The actual bits of the value could be interpreted as any 4 byte type (float is 4-bytes on Arduino) and int32_t is a 4 byte type.

So, a couple of questions:

  1. Why doesn't the compiler have a similar problem with 8728787 as a parameter? That should be just as ambiguous, no?
  2. How can I make this work? Is there any way to accept all these types as both variables and literals?

Note: I don't want to use a template function because I need to handle the different types with different algorithms.

Note note: I have solved this for now by simply creating multiple functions i.e. fncInt, fncStr, fncBool, etc. This works but...it rubs my OCD the wrong way.

Note note note: This will be part of a library. I'd also like to avoid forcing whoever uses it to do fnc((float) 2.718) for the same reasons as the previous note. Maybe it's silly. I dunno.


Update: help from Miles Budnik and 273K much appreciated. It does indeed compile if I change func(float d) to func(double d).

However, if I try passing an int it fails again.

int anInt2 = 12;
int32_t anInt = 29339222;
float aFloat = 3.1415;

void setup() {
  Serial.begin(9600);
  fnc(anInt);
  fnc(aFloat);

  fnc(8728787);
  fnc(2.718);
  fnc(anInt2);
}

sketch_apr1a.ino:28:13: error: call of overloaded 'fnc(int&)' is ambiguous fnc(anInt2);

Also, if I change from int32 to int64, it also fails, again on the double literal.

void fnc(int64_t d){
  //Serial.println((String) "int32 = " + d);
}

int64_t anInt = 29339222;
float aFloat = 3.1415;

void setup() {
  Serial.begin(9600);
  fnc(anInt);
  fnc(aFloat);
  fnc(8728787);
  fnc(2.718);
  fnc(anInt2);
}

sketch_apr1a.ino:27:14: error: call of overloaded 'fnc(long int)' is ambiguous fnc(8728787); ^

I appreciate the advice, I have indeed tried understanding this from the books; I'm afraid I'm just quite confused by the whole thing. Thank you for your help.

Robert M.
  • 575
  • 5
  • 17
  • 3
    *the parameter provides the compiler with no type information* -- this is wrong assumption. It's better for you if you read any of [good C++ books](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list). `2.718` is `double`, that can be casted equally to any of the `int`, `bool`, `float` overloaded functions. *`fnc((float) 2.718)`* -- call `fnc(2.718f)` or add an overloaded function for `double`. – 273K Apr 02 '23 at 01:56
  • 1
    `char* aStr = "baloney";` -- compiler should at least warn you here about a bad chosen pointer type. – 273K Apr 02 '23 at 02:02
  • @273K I see. If I change fnc(float) to fnc(double) it does work, however it fails if I pass fnc an int (not int32_t). Also, if I change int32_t to int64_t, it fails again. – Robert M. Apr 02 '23 at 05:09
  • Why is char* a bad pointer type? I was under the impression that char* something was essentially the same as char something[] (For instance see this thread: https://forum.arduino.cc/t/only-for-c-experts-how-to-place-string-to-progmem-w-o-array/601714/2?u=cinerobert) – Robert M. Apr 02 '23 at 05:11
  • 1
    @RobertM. — the type of a string literal (e.g. `”baloney”`) is `const char[N]`. When used it decays to `const char*`. Back in the olden days you could store that pointer in a `char*`, but not anymore. You need to use `const char*`. – Pete Becker Apr 02 '23 at 14:16
  • @PeteBecker Ah, I see, thank you. It does seem to be working though...are there some unseen problems that could crop up in other contexts? – Robert M. Apr 02 '23 at 18:08

1 Answers1

3

Your issue is that the literal 2.718 has the type double, not float, and there is no fnc(double) overload. C++ allows up to one built-in conversion and one user-defined conversion to be implicitly added when performing overload resolution. There are built-in conversions from double to int32_t, bool, and float and none of them are considered "better" than any of the others by the compiler since they are all lossy conversions. Thus the call is ambiguous because it doesn't know if you want to call fnc(int32_t), fnc(bool) or fnc(float).

If you made your floating-point overload accept a double instead of a float there would be an exact match, which is considered "better" than a match requiring a conversion, so that would be chosen.

Also note that float to double conversion is also considered "better" than the others mentioned since it's lossless, while the others are lossy, so for a call to fnc(aFloat) the compiler will also choose fnc(double) over fnc(int32_t) or fnc(bool).

Alternatively, you can append the f suffix to your floating-point literal to explicitly make it a float. For example fnc(2.718f) is not ambiguous since it's an exact match for fnc(float).


  1. Why doesn't the compiler have a similar problem with 8728787 as a parameter? That should be just as ambiguous, no?

This is a bit complicated. On Arduino, int can be either 16-bit or 32-bit, depending on the exact board.

  • The type of the literal 8728787 will be either int (if int is 32-bit) or long int (if int is 16-bit) since 8728787 is too big to be represented in 16 bits.
  • int32_t will be an alias for either int (if int is 32-bit) or long int (if int is 16-bit).

In either case, the type of 8728787 will exactly match int32_t. Since it's an exact match, fnc(int32_t) will be chosen over the other options.

If you tried to pass a 64-bit integer to fnc you would get the same ambiguity as the double to float case. For example, the following is ambiguous for the same reason as the double call:

int64_t aLongInt = 123;
fnc(aLongInt);
Miles Budnek
  • 28,216
  • 2
  • 35
  • 52
  • 1
    I like your answer (upvoted). But perhaps you could improve it slightly by mentioning the fact that the language allows for *one* implicit conversion - no more. Nitpicking. – Jesper Juhl Apr 02 '23 at 02:39
  • 2
    @JesperJuhl Added. Technically two conversions are allowed (one built-in and one user-defined). – Miles Budnek Apr 02 '23 at 02:48
  • That's very interesting, thank you (upvoted). I'm still puzzled about some things though: 1) If I pass fnc an int, it has the same ambiguity problem again. 2) Separately (not passing an int) if I change all int32_t to int64_t, including the function parameter, the compiler again has an ambiguity error when passing the literal 8728787. – Robert M. Apr 02 '23 at 05:05
  • Would it be helpful to edit my original question to represent this? – Robert M. Apr 02 '23 at 05:07