c++ 如果未实现函数size_t Print::print(unsigned long long n,int base),则避免调用该函数

64jmpszr  于 2022-12-05  发布在  其他
关注(0)|答案(2)|浏览(170)

我维护了一个Arduino库,它使用下面的代码(简化)来打印通过红外线接收到的结果。

unsigned long long decodedData; // for 8 and 16 bit cores it is unsigned  long decodedData;
Print MySerial;
MySerial.print(decodedData, 16);

大多数32位arduino内核都提供了size_t Print::print(unsigned long long n, int base)函数,并且编译时没有错误。
但是有32位内核,它们不提供size_t Print::print(unsigned long long n, int base),它们只提供size_t Print::print(unsigned long n, int base),在那里我得到了预期的编译时错误call of overloaded 'print(decodedData, int)' is ambiguous
我试着理解Check if a class has a member function of a given signature,但还是没有头绪。
我要用

MySerial.print((uint32_t)(decodedData >> 32), 16);
    MySerial.print((uint32_t)decodedData & 0xFFFFFFFF, 16);

如果没有提供函数size_t Print::print(unsigned long long n, int base)
我试过了

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

而这是可行的(感谢雷米勒博):-)。
但此检查不起作用,因为它仍然引用long long print函数(更新:而使用if constexpr ()(并非对所有内核都可用)也没有帮助)。

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

有没有可能避免这个编译错误?
顺便说一句,我不想用2个32位打印来代替所有64位打印,只想用一个很少使用和懒惰的32位核心,因为所有的主流核心都能很好地使用64位打印。

vmpqdwk3

vmpqdwk31#

使用C++11,您可以执行以下操作:

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

第二个版本,具有函式多载和标准型别:

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

fkvaft9z2#

问题是文档太少。我找到了this,但它没有提供Serial::print重载的定义。
我猜想它看起来是这样的:

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

所以当你在unsigned long long上使用这个时,你期望它与uint64_t匹配重载,但是在某些平台上uint64_t不是unsigned long long,而是unsigned long。这种不匹配导致重载解析无法决定使用哪个回退并报告错误:call of overloaded 'print(decodedData, int)' is ambiguous .
因此,与其用“避免调用函数”来使你的生活复杂化,不如显式地使用类型uint64_t来修复你的代码。注意,这个类型定义解释了你的意图,所以你应该明确地使用它。

uint64_t decodedData;
Print MySerial;
MySerial.print(decodedData, 16);

如果我错了,请提供更好的链接到这个API文档。也包括完整的错误日志时,建立失败,所以我们可以看到什么重载是可用的。

相关问题