为何make_something<shared_ptr<int>>(nullptr)会匹配std::function重载?
要搞清楚这个问题,我们需要拆解重载决议的优先级规则和模板特化关系,结合每个调用的具体情况分析:
先明确两个重载的本质
我们有两个模板函数:
显式指定参数时变为右值引用的模板:
template <class Type> void make_something(Type&& a)当显式指定
Type为std::shared_ptr<int>时,参数类型是std::shared_ptr<int>&&(右值引用)。std::function专属模板:template <class Type> void make_something(std::function<Type()>)当显式指定
Type为std::shared_ptr<int>时,参数类型是std::function<std::shared_ptr<int>()>。
逐个分析调用匹配逻辑
1. make_something<int>(1)
- 第一个重载参数是
int&&,1是int类型,属于精确匹配,无需任何转换。 - 第二个重载需要将
1转换为std::function<int()>,这是用户定义转换。 - 精确匹配优先级远高于用户定义转换,所以选第一个重载。
2. make_something<int>(nullptr)
- 第一个重载参数是
int&&,nullptr是std::nullptr_t类型,无法转换为int(非指针类型不能从nullptr构造),因此第一个重载不可行。 - 第二个重载可以通过
std::function的nullptr_t构造函数,将nullptr隐式转换为std::function<int()>,所以只能选第二个重载。
3. make_something<int*>(nullptr)
- 第一个重载参数是
int*&&,nullptr可以标准转换为int*(nullptr_t到任意指针类型的转换是C++标准规定的),然后绑定到右值引用,属于标准转换序列。 - 第二个重载需要将
nullptr转换为std::function<int*()>,是用户定义转换。 - 标准转换序列优先级高于用户定义转换,所以选第一个重载。
4. make_something<std::shared_ptr<int>>(nullptr)(核心问题)
这里两个重载都可行,但第二个被选中,原因有两点:
转换序列的优先级相同
- 第一个重载:
nullptr需要通过std::shared_ptr的nullptr_t构造函数转换为std::shared_ptr<int>(用户定义转换),再绑定到std::shared_ptr<int>&&(身份转换,属于标准转换的一部分),整体是用户定义转换序列。 - 第二个重载:
nullptr直接通过std::function的nullptr_t构造函数转换为std::function<std::shared_ptr<int>()>,也是用户定义转换序列。 - 两者的转换序列优先级相同,此时需要看模板的特化程度。
模板特化程度决定优先级
C++重载决议中,当两个模板实例化后的函数都可行时,更特化的模板会被优先选中:
- 第一个模板
make_something(Type&&)是通用模板,可以接受任意类型的参数,泛化程度极高。 - 第二个模板
make_something(std::function<Type()>)是针对std::function类型的专属模板,泛化程度更低、更特化。 - 因此,编译器会优先选择更特化的第二个模板,最终输出
std::function overload。
内容的提问来源于stack exchange,提问作者auzn
相关产品推荐
相关产品推荐

