显式指定模板参数时,重载解析为何选择错误重载?
原代码
#include <iostream> #include <memory> #include <queue> template<typename TValue> [[maybe_unused]] constexpr auto t1(const std::queue<TValue> &value) -> void { std::queue<TValue> temp = value; while (!temp.empty()) { std::cout << temp.front() << std::endl; temp.pop(); } } template<typename TValue> constexpr auto t1(const TValue &nm) -> void { std::cout << nm << std::endl; } template<typename TValue> constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void { t1<TValue>(*nm); } int main(int argc, char **argv) { std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7); t1(s_ptr); return 0; }
编译错误
error C2338: static_assert failed: 'The C++ Standard forbids containers of const elements because allocator is ill-formed.'
尝试的修改与结果
- 修改shared_ptr模板函数,仍报相同错误:
template<typename TValue> constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void { const int temp = *nm; t1<TValue>(temp); }
- 通过static_assert确认
TValue为const int:
template<typename TValue> constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void { if constexpr (std::is_same_v<TValue, const int>){ static_assert(false, "??"); } t1<TValue>(*nm); }
- 移除显式模板参数
<TValue>,代码正常运行:
template<typename TValue> constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void { t1(*nm); }
- 使用
std::remove_cv_t,代码正常运行:
template<typename TValue> constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void { t1<std::remove_cv_t<TValue>>(*nm); }
- 删除std::queue相关重载,代码正常运行:
template<typename TValue> constexpr auto t1(const TValue &nm) -> void { std::cout << nm << std::endl; } template<typename TValue> constexpr auto t1(const std::shared_ptr<TValue> &nm) -> void { t1<TValue>(*nm); } int main(int argc, char **argv) { std::shared_ptr<const int> s_ptr = std::make_shared<const int>(7); t1(s_ptr); return 0; }
疑问
当TValue为const int时,模板为何会选择std::queue的重载,而非预期的通用TValue重载?
解答
核心原因是模板重载解析规则与实例化错误的性质:
显式指定模板参数的影响
当你写t1<TValue>(*nm)时,是显式指定了模板参数为const int,此时编译器会遍历所有t1模板,尝试用const int作为模板参数实例化每个重载,再检查实例化后的函数是否能接受传入的const int&参数。std::queue重载的实例化错误是硬错误
对于std::queue<TValue>的重载,编译器会尝试实例化t1<const int>(const std::queue<const int>&)。虽然C++标准禁止容器存储const类型元素(分配器无法构造const对象),但这个错误是在模板实例化阶段触发的static_assert硬错误,不属于SFINAE(替换失败不是错误)的范畴。编译器不会因为这个实例化错误排除该重载,而是直接报错。自动推导时的行为差异
当你不指定模板参数,让编译器自动推导时,它会尝试为每个重载推导模板参数:对于std::queue的重载,无法推导出TValue使得const std::queue<TValue>&匹配const int&,因此这个重载会被直接排除,不会进入实例化阶段,自然不会触发错误,最终调用通用的t1(const TValue&)重载。其他修复方案的原理
- 使用
std::remove_cv_t<TValue>时,模板参数变为int,此时std::queue<int>的重载参数是const std::queue<int>&,与传入的const int&不匹配,因此该重载被排除,调用通用重载。 - 删除
std::queue重载后,没有了这个有问题的候选,直接调用通用重载。
内容的提问来源于stack exchange,提问作者MikuSoft

