c++arduinooverloadingliteralsambiguous-call

overloading functions to use with both variables and literals - ambiguous call error


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.


Solution

  • 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.

    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);