简体   繁体   中英

Avoid calling of function size_t Print::print(unsigned long long n, int base) if it is not implemented

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.

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM