Function Overload seams to work wrong!

I am currently working on a class to process json data to communicate with android via BT. Everything works so far, however, there is a problem with the overloading of functions.

It seems as if the compiler use the wrong function. I have written a sample program to test the behavior.
Instead of the overloaded string function, the Bool function is used. Can someone explain why it is?

I am using Arduino IDE (.org) 1.8.1.

void setup() {
  Serial.begin(115200);//or 9600
  Test("small text");
  Test("some text");
  String text = "more text";
  Test(text);
  Test((String)"thats text");
}

void loop() { }

void Test(String text){
  Serial.println("got string '" + text + "'");
}
void Test(bool trueFalse){
  Serial.println("got bool '" + (String)(trueFalse?"true":"false") + "'");
}

Serial Monitor tells:
got bool 'true'
got bool 'true'
got string 'more text'
got string 'thats text'

:o

Because a string is not a String?

Which is already clear, but why is the overloaded function with "Bool" addressed when string / char[] is passed and Overloading for String is available? This is not logical from my point of view

Added...

void Test(char text[]){
  Serial.print("got string '"); Serial.print(text); Serial.println("'");
}

and works, but with warning "warning: deprecated conversion from string constant to 'char*'"

Hmmmm.

Because the compiler can't decide how to convert a string to a String, so it picks it best option available to it.
There's no automatic promotion (if that's the right way to think about String) available, so evaluate the pointer. It's non-null, therefore true.

Try changing

void Test(String text){

to

void Test(String* text){

Mark

@Groove

For me not understandable.

I would have expected the compiler to convert a string/char[] to a String (which consists of characters) and not to a bool value!
No matter, I'll cast the string into a String! Anyway thanks and nice weekend. 8)

DrDooom:
I would have expected the compiler to convert a string/char[] to a String (which consists of characters) and not to a bool value!

The compiler can't perform type conversions where there's no obvious conversion to perform.
To you a String and a string might look related, but fundamentally, they're about as similar as a hamster and a nuclear reactor.

Because "foo" is not a String - it is an array of char. The compiler finds the best match, which is to treat it as a numeric quantity (a pointer) which is then treated as a bolean (true if its nonzero).

Ok, thank u both.

By the Way:

Testet with VS2015 -> C# (.Net 4):

Result:
got String 'small text'
got String 'some text'
got String 'more text'
got String 'thats text'

Testet with VS2015 -> Visual C++ (Console):

Result:
got bool 'true'
got bool 'true'
got string '...'
got string '...'

Testet with Android Dev Studio 2.0 (Android 6.0.1):

Result:
got String 'small text'
got String 'some text'
got String 'more text'
got String 'thats text'

I will keep that in Mind.... that i will never be best friend with my compiler! :slight_smile:

Think of it this way:
There's nothing in the language rules that says a char array has to contain a null-terminated string - it could for instance, specify a MAC address, or the state of some inputs - so there is absolutely no necessity to assume that it can, or should, be automatically converted to a String.

DrDooom:
Which is already clear, but why is the overloaded function with "Bool" addressed when string / char[] is passed and Overloading for String is available? This is not logical from my point of view

Added...

void Test(char text[]){

Serial.print("got string '"); Serial.print(text); Serial.println("'");
}



and works, but with warning "warning: deprecated conversion from string constant to 'char*'"

Hmmmm.

To solve that just change it to ...

void Test(const char text[]){
  Serial.print("got string '"); Serial.print(text); Serial.println("'");
}

Literal strings like "small text" can't be modified by the function hence the warning (not that your function even tries to modify it). Adding the const keyword there just makes it clear to the compiler that you are never going to try to modify the passed string, so the warning should go.

Groove:
Think of it this way:
There's nothing in the language rules that says a char array has to contain a null-terminated string - it could for instance, specify a MAC address, or the state of some inputs - so there is absolutely no necessity to assume that it can, or should, be automatically converted to a String.

The String class has an assignment operator and copy constructor for a char pointer; so this conversion is well defined.

You can see this in use on the fourth line from setup in the OP's code. The reason this code is not working is because the C++ overload selection rules do not use user defined conversions in their deduction.

If the functions weren't overloaded (only the function accepting String) then calling the function using a char pointer or char array would work fine.

Two methods of fixing this have already been discussed. Either casting the input to a String, or overloading the function again with a more specific parameter (char pointer).

There is a third option, and it is to make the bool function only accept a bool as input, not types converted to a bool. This will prevent the string literal from implicitly casting to bool.

Karma ching!

There is a third option, and it is to make the bool function only accept a bool as input, not types converted to a bool. This will prevent the string literal from implicitly casting to bool.

I have searched for a solution. Unfortunately without success. Can you tell me how I can tell a function, that type conversion is not wanted?!

And PLEASE no LMGTFU :wink:

DrDooom:
Karma ching!

I have searched for a solution. Unfortunately without success. Can you tell me how I can tell a function, that type conversion is not wanted?!

And PLEASE no LMGTFU :wink:

Don't worry, its not something that a single google search can produce. I'll do up an example and post it shortly.

DrDooom:
I have searched for a solution. Unfortunately without success. Can you tell me how I can tell a function, that type conversion is not wanted?

Hey I'm keen to know that one too. :slight_smile:

Anyway, I think the most basic solution by far is just to write an overload function for EVERY type that you intend to pass.

If you want the same operations to happen for both char* and String, then simply write the overload function initially for char*, and then write the overload for String to explicitly call the above.

For example (Edit: change printf back to Serial.print)

void Test(const char* text){
  printf("got string '%s'\n",text);
}

void Test(String text){
  Test(text.c_str());
}

Sorry I took so long, was away getting some lunch and chilling.

To get the boolean overload to only accept a bool directly will require some modern C++ techniques. Modern C++ has moved away from OOP techniques and towards something known as "generic programming" (known as Parametric polymorphism in CS). In C++ generic programming is a way to do decision making during compile time. This is done in C++ using templates. So the first thing we need to do is change the boolean overload to a template.

P.S this is not an easy topic, so please ask questions if you need.

void Test(String text){
  Serial.println("got string '" + text + "'");
}

template< typename T >
void Test( T trueFalse ){
  Serial.println("got bool '" + (String)(trueFalse?"true":"false") + "'");
}

Ok, as it stands this isn't too useful, as now it will accept any type, and could even override the String overload breaking the code elsewhere.

But what this has given us is a way to see the real type passed in, as no conversions will take place. If you pass in a string literal, then T will be a reference to a const char array. So this is solving the problem of the pointer casting to a bool.

The second half of this problem is getting the template to only be selected when T is a bool. This can be done with a modern C++ technique called SFINAE (selection failure is not an error).

What SFINAE means is when the compiler is deducing which template to use (different to overloaded function resolution) if a particular candidate is ill formed, it is removed from the list of possible candidates, but it does not generate an error. This is different to overloaded functions as an ill formed overload is an error and will halt compilation.

So what we need to do is make the template ill formed when anything other than a boolean is passed in... easy right.

This is where generics come into play. First we need some way to compare the type T with a bool to see if they are the same. The C++ standard library provides many generics which can solve this, but its not a part of the AVR core. But that is ok as we can write our own.

template < typename T, typename U > struct is_same { enum{value = false}; };
template < typename T > struct is_same< T, T > { enum{value = true}; };

This may look complex, but it is really a simple idea. You call this generic with two types, and if they are the same, the second specialization is selected and value becomes true.
I.E.

is_same< bool, char >::value; //equals false.
is_same< bool, bool >::value; //equals true.

The next thing we need is a way to make the function ill formed if T is not the same as bool. C++ has a standard way of doing this and it is called enable_if, again we have to recreate it.

template< bool V, typename T = void > struct enable_if{};
template< typename T > struct enable_if< true, T >{ typedef T type; };

This generic simply takes a boolean value as a parameter. If it is true, then it will contain a typedef named type, and if false it doesn't. This can be used to create ill formed code:

enable_if< true >::type //fine, type exists.
enable_if< false >::type //error, type does not exist.

We can then combine these to make a compile time decision.

enable_if< is_same< T, bool >::value >::type //If T is a bool we want to enable our function.

Finally we have to put these generics to use with out template function. Then if enable_if is false, the template function will be ill formed and not be used in selection.

template< typename T,
          typename U = typename enable_if< is_same< T, bool >::value >::type >
void Test( T trueFalse ){
  Serial.println("got bool '" + (String)(trueFalse?"true":"false") + "'");
}

In this final product, U will not be defined if T is not a bool. And rather than specifying a type in the parameter list, the function is a template which only accepts a certain type; templates do not have to be re-usable generic code.

This may not look as nice as the code you are used to but its the future of C++, and its not until you have some quite complex designs that the usefulness becomes apparent. However, for an academic point of view, it is something to look into.

Due to the Arduino IDE we have to change a few things to get it to compile. First the template function needs to be all on one line, and it needs to be above the code that uses it as no prototype is generated for anything but simple templates. You can get around this by putting the template stuff in a header file.

Here is a complete working sketch.

template< bool V, typename T = void > struct enable_if{};
template< typename T > struct enable_if< true, T >{ typedef T type; };

template < typename T, typename U > struct is_same {enum{value = false};};
template < typename T > struct is_same< T, T > {enum{value = true};};

template< class T, class = typename enable_if< is_same<T,bool>::value >::type> void Test(T trueFalse){
  Serial.println("got bool '" + (String)(trueFalse?"true":"false") + "'");
}

void Test(String text){
  Serial.println("got string '" + text + "'");
}

void setup() {
  Serial.begin(9600);//or 9600

  Test("small text");
  Test("some text");
  String text = "more text";
  Test(text);
  Test((String)"thats text");
  
  bool b = true;
  Test(b);
 Test(false);
}
 
void loop() { }

And if you're not bleeding from the ear, I hope this has given you a little insight into the complex world of C++ generic programming.

Some topics you can google which may make for some good reading:

Generic programming
Template meta programming
SFINAE
C++ typetraits

Can you specify overloading of casting char* to String that will cause the compiler to prefer that to casting to Boolean?

westfw:
Can you specify overloading of casting char* to String that will cause the compiler to prefer that to casting to Boolean?

Of course! This is one of the options specified by a poster above. The pointer overload can simply call the String version using a cast.

Puh....pYro_65!

Since C ++ 98 "for me" is already complicated and complex enough .... I will implement another overload.
There are some things in c ++, which are not yet clear to me. However, as my project is still growing and there will still be some hurdles, a simple further overload is the best solution at the moment.

Anyway thanks for the detailed presentation of templates. I hope it will never be necessary that I completely understand your post. :O)

Thank you all!