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