如何避免未实现size_t Print::print(unsigned long long,int)时的编译错误?
问题背景
我维护一个Arduino库,使用以下简化代码打印红外接收结果:
unsigned long long decodedData; // 8位和16位内核中为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 n, int base),此时会触发编译错误:
call of overloaded 'print(decodedData, int)' is ambiguous
需求是:仅针对该小众内核,将64位打印拆分为两次32位打印;主流内核保留原生64位print调用,不做降级。
已实现SFINAE检测结构体判断64位print是否存在,但普通条件判断会因无效分支代码引发编译错误,且目标内核不支持if constexpr。
解决方案:SFINAE驱动的重载分流
核心思路是通过编译时重载决议,让编译器自动选择适配当前内核的打印逻辑,避免无效分支代码被编译。
1. 完善64位Print函数检测
替换原有检测结构体为更简洁的表达式SFINAE版本(兼容C++11及以上,Arduino主流内核均支持):
template<typename T> struct has_uint64_print { private: // 尝试调用print(unsigned long long, int),成功则返回char类型 template<typename U> static auto test(int) -> decltype(std::declval<U>().print(std::declval<unsigned long long>(), 16), char()); // 匹配所有其他情况,返回int类型 template<typename U> static int test(...); public: static constexpr bool value = sizeof(test<T>(0)) == sizeof(char); };
如果目标内核未提供std::declval,可以用指针替代:
template<typename U> static auto test(int) -> decltype(((U*)nullptr)->print(0ULL, 16), char());
2. 编写条件重载的打印函数
利用std::enable_if(或手动实现的替代版本)控制两个重载函数的实例化:
#include <type_traits> // 内核支持64位print时的版本:直接调用原生函数 template<typename PrintType> typename std::enable_if<has_uint64_print<PrintType>::value, void>::type print_decoded_data(PrintType& serial, unsigned long long data) { serial.print(data, 16); } // 内核不支持64位print时的版本:拆分32位打印 template<typename PrintType> typename std::enable_if<!has_uint64_print<PrintType>::value, void>::type print_decoded_data(PrintType& serial, unsigned long long data) { serial.print((uint32_t)(data >> 32), 16); serial.print((uint32_t)data & 0xFFFFFFFFULL, 16); }
兼容无std::enable_if的内核:手动实现基础版enable_if:
template<bool B, typename T = void> struct enable_if {}; template<typename T> struct enable_if<true, T> { using type = T; };
然后将代码中的std::enable_if替换为enable_if即可。
3. 替换原有调用代码
把原来的MySerial.print(decodedData, 16);替换为:
unsigned long long decodedData; Print MySerial; print_decoded_data(MySerial, decodedData);
原理说明
编译时,编译器会根据has_uint64_print<Print>::value的布尔值,仅实例化符合当前内核的重载函数:
- 主流内核:实例化直接调用64位
print的版本,保留原生效率。 - 小众内核:实例化拆分打印的版本,完全不涉及对缺失的64位
print函数的引用,避免编译歧义。
替代方案:针对小众内核的模板特化
如果已知小众内核的专属编译宏(比如#define NO_UINT64_PRINT 1),可以用条件编译+模板特化实现更简单的适配:
// 默认版本:调用原生64位print template<typename PrintType> void print_decoded_data(PrintType& serial, unsigned long long data) { serial.print(data, 16); } // 针对小众内核的特化版本 #ifdef NO_UINT64_PRINT template<> void print_decoded_data<Print>(Print& serial, unsigned long long data) { serial.print((uint32_t)(data >> 32), 16); serial.print((uint32_t)data & 0xFFFFFFFFULL, 16); } #endif
此方案更简洁,但依赖小众内核的专属宏定义,灵活性不如SFINAE方案。
内容的提问来源于stack exchange,提问作者Armin J.

