Avoid calling of function size_t Print::print(unsigned long long n, int base) if it is not implemented
Problem Description:
I maintain an Arduino library which uses the following code (simplified) to print results received by infrared.
unsigned long long decodedData; // for 8 and 16 bit cores it is unsigned long decodedData;
Print MySerial;
MySerial.print(decodedData, 16);
Most of the 32 bit arduino cores provide the function size_t Print::print(unsigned long long n, int base)
and compile without errors.
But there are 32 bit cores, which do not provide size_t Print::print(unsigned long long n, int base)
, they only provide size_t Print::print(unsigned long n, int base)
and there I get the expected compile time error
call of overloaded 'print(decodedData, int)' is ambiguous
.
I tried to understand Check if a class has a member function of a given signature but still have no clue.
I want to use
MySerial.print((uint32_t)(decodedData >> 32), 16);
MySerial.print((uint32_t)decodedData & 0xFFFFFFFF, 16);
in case the function size_t Print::print(unsigned long long n, int base)
is not provided.
I tried
template<typename T>
struct has_uint64_print {
template<typename U, size_t (U::*)(unsigned long long, int)> struct SFINAE {
};
template<typename U> static char test(SFINAE<U, &U::print>*);
template<typename U>
static int test(...);
static const bool has64BitPrint = sizeof(test<T>(nullptr)) == sizeof(char);
};
and this works (Thanks to Remy Lebeau) :-).
But this check does not work, since it still references the long long print function (update: and using if constexpr ()
-which is not available for all cores- does not help).
if(has_uint64_print<Print>::has64BitPrint){
MySerial.print(decodedData, 16);
} else {
MySerial.print((uint32_t)(decodedData >> 32), 16);
MySerial.print((uint32_t)decodedData & 0xFFFFFFFF, 16);
}
Is there any chance to avoid this compile error?
BTW. I do not want to substitute all occurences of the 64 bit print with the 2 32 bit prints, only for one seldom used and lazy implemented 32 bit core, since all mainsteam cores work well with the 64 bit print.
Solution – 1
With C++11 you can do something like this:
#include <iostream>
#include <iomanip>
#include <type_traits>
// First implementation of printer
class Impl1 {
public:
static void print(uint64_t value, int base) {
std::cout << "64-bit print: " << std::setbase(base) << value << "n";
}
};
// Second implementation of printer
class Impl2 {
public:
static void print(uint32_t value, int base) {
std::cout << "32-bit print: " << std::setbase(base) << value << "n";
}
};
// Template to automatically select proper version
template<typename Impl, typename = void>
class Print;
template<typename Impl>
class Print<Impl, typename std::enable_if<std::is_same<decltype(Impl::print), void(uint64_t, int)>::value>::type>
{
public:
static void print(uint64_t value, int base)
{
Impl::print(value, base);
}
};
template<typename Impl>
class Print<Impl, typename std::enable_if<std::is_same<decltype(Impl::print), void(uint32_t, int)>::value>::type>
{
public:
static void print(uint64_t value, int base)
{
Impl::print(static_cast<uint32_t>(value >> 32), base);
Impl::print(static_cast<uint32_t>(value), base);
}
};
int main()
{
Print<Impl1>::print(0x100000001, 16);
Print<Impl2>::print(0x100000001, 16);
}
Second version, with function overloads and standard types:
#include <iomanip>
#include <iostream>
#include <type_traits>
// Set to 1 to see effect of using 64 bit version
#define HAS_64 0
class Print {
public:
size_t print(unsigned int value, int base) {
return base + 1; // dummy
};
size_t print(long value, int base) {
return base + 2; // dummy
};
size_t print(unsigned long value, int base) {
return base + 3; // dummy
};
#if HAS_64
size_t print(unsigned long long value, int base) {
return base + 4; // dummy
};
#endif
};
Print MySerial;
// If you have C++17 you can just use std::void_t, or use this for all versions
#if __cpp_lib_void_t >= 201411L
template<typename T>
using void_t = std::void_t<T>;
#else
template<typename... Ts> struct make_void { typedef void type; };
template<typename... Ts> using void_t = typename make_void<Ts...>::type;
#endif
// Detecting if we have 'print(unsigned long long value, int base)' overload
template<typename T, typename = void>
struct has_ull_print : std::false_type { };
template<typename T>
struct has_ull_print<T, void_t<decltype(std::declval<T>().print(0ull, 0))>> : std::true_type { };
// Can be either class or namesapce
namespace PrintXYZ {
template <typename Impl, typename std::enable_if<!has_ull_print<Impl>::value, bool>::type = true>
size_t print(Impl &p, unsigned long long value, int base) {
p.print(static_cast<uint32_t>(value >> 32), base);
p.print(static_cast<uint32_t>(value), base);
return 0; // Not sure about return value here.
}
template <typename Impl, typename std::enable_if<has_ull_print<Impl>::value, bool>::type = true>
size_t print(Impl &p, unsigned long long value, int base) {
return p.print(value, base);
}
};
int main() {
PrintXYZ::print(MySerial, 0x100000001, 16);
}
Solution – 2
Problem is poor documentation. I’ve found this, but it doesn’t provide definition of Serial::print
overloads.
I suspect it looks like this:
class Serial
{
public:
...
void print(uint8_t x, int base);
void print(uint16_t x, int base);
void print(uint32_t x, int base);
void print(uint64_t x, int base);
};
So when you use this with unsigned long long
you are expecting it matches overload with uint64_t
, but on some platforms uint64_t
is not a unsigned long long
, but unsigned long
. This mismatch leads to situation that overload resolution can’t decide which fallback use and reports error: call of overloaded 'print(decodedData, int)' is ambiguous
.
So instead complicate your life with "Avoid calling of function", just fix your code by explicitly use type uint64_t
. Note this type definition explains your intent, so you should use it definitely.
uint64_t decodedData;
Print MySerial;
MySerial.print(decodedData, 16);
If I’m wrong pleas provide better link to this API documentation.
Also include full error log when build fails, so we can see what overloads are available.