重载与requires子句:编译器会选择哪个重载版本?
问题分析:模板重载中requires子句在函数指针推导时的行为差异
你的代码里出现的问题并非对requires子句的误解,而是当前Apple clang 16.0.0在处理模板函数赋值给函数指针时的重载决议逻辑不符合C++20标准预期。
直接调用与函数指针推导的差异
1. 直接调用的正常行为
当你直接调用foo(static_cast<unsigned char>(0))时,C++20的重载决议规则会正常生效:
- 两个模板都满足
std::unsigned_integral<T>的约束; - 第二个模板额外的
sizeof(T) == 1约束对unsigned char成立,属于更严格的约束特化,因此会被优先选择,返回0+1=1,第一个断言通过。
2. 函数指针赋值的异常行为
当执行unsigned int (*function)(unsigned char x) = foo;时,clang的推导逻辑出现偏差:
- 编译器会为两个模板推导实参
T=unsigned char,且都满足各自的约束; - 按照C++20标准,此时应该选择约束更严格的第二个模板实例,但clang却选择了第一个无额外requires约束的模板,导致
function(0)返回0,触发断言失败。
进一步验证:交换两个模板的定义顺序,会发现函数指针会选择定义在前的模板,这说明clang在该场景下是根据模板的定义顺序而非约束特化优先级来选择实例,这不符合标准要求。
解决方法
方法1:显式指定模板实参
通过显式指定模板实参,强制编译器实例化目标版本:
unsigned int (*function)(unsigned char x) = &foo<unsigned char>;
此时编译器会明确选择满足sizeof(T)==1的第二个模板,断言正常通过。
方法2:改为显式特化
将第二个模板改为针对unsigned char的显式特化,特化版本会在重载决议中被优先选择:
#include <cassert> #include <concepts> template<std::unsigned_integral T> constexpr unsigned int foo(T x) { return x; } // 改为显式特化 template<> constexpr unsigned int foo(unsigned char x) { return x + 1; } int main() { assert(foo(static_cast<unsigned char>(0)) == 1); unsigned int (*function)(unsigned char x) = foo; assert(function(0) == 1); }
内容的提问来源于stack exchange,提问作者Derek
相关产品推荐
相关产品推荐

