右值std::ostringstream与std::unique_ptr结合时operator<<重载未匹配问题
临时std::ostringstream与std::unique_ptr结合时operator<<重载未选中问题
问题现象
当临时std::ostringstream对象与std::unique_ptr结合使用时,针对右值ostream的std::basic_ostream转发重载未被选中,导致编译失败。
代码示例
#include <iostream> #include <memory> #include <sstream> std::ostream& operator<<(std::ostream& os, const std::unique_ptr<int>& /*res*/) { os << "Let's print something" << std::endl; return os; } int main() { std::unique_ptr<int> res; std::ostringstream {} << res; return 0; }
编译错误(GCC 14.2 + C++17)
error: no match for 'operator<<' (operand types are 'std::ostringstream' {aka 'std::__cxx11::basic_ostringstream<char>'} and 'std::unique_ptr<int>')
初步分析
用户定义的operator<<重载接受std::ostream&(非const左值引用),无法直接绑定到临时的右值std::ostringstream对象,因此该重载不会被编译器选中。
预期会被选中的是std命名空间中的右值转发模板重载:
template< class Ostream, class T > Ostream&& operator<<( Ostream&& os, const T& value );
但GCC针对该重载的模板参数推导失败,错误信息如下:
/opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/ostream:807:5: note: candidate: 'template<class _Ostream, class _Tp> _Ostream&& std::operator<<(_Ostream&&, const _Tp&)' 807 | operator<<(_Ostream&& __os, const _Tp& __x) | ^~~~~~~~ /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/ostream:807:5: note: template argument deduction/substitution failed: /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/ostream: In substitution of 'template<class _Ostream, class _Tp> _Ostream&& std::operator<<(_Ostream&&, const _Tp&) [with _Ostream = std::__cxx11::basic_ostringstream<char>; _Tp = std::unique_ptr<int>]': <source>:14:30: required from here 14 | std::ostringstream {} << res; | ^~~ /opt/compiler-explorer/gcc-14.2.0/include/c++/14.2.0/ostream:791:46: error: no match for 'operator<<' (operand types are 'std::__cxx11::basic_ostringstream<char>' and 'const std::unique_ptr<int>') 791 | = decltype(std::declval<_Os&>() << std::declval<const _Tp&>())> | ~~~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
核心疑问
为什么当_Os = std::ostringstream且_Tp = std::unique_ptr<int>时,decltype(std::declval<_Os&>() << std::declval<const _Tp&>())会判定为非法?
原因解析
问题出在名字查找的范围限制:
- 右值转发模板位于
std命名空间内,当模板执行SFINAE合法性检查时,函数调用std::declval<_Os&>() << std::declval<const _Tp&>()的名字查找会优先在std命名空间内进行; - ADL(参数依赖查找)仅会查找参数类型关联的命名空间:
std::ostringstream和std::unique_ptr都属于std,因此ADL也只会在std内部查找; - 用户定义的
operator<<位于全局命名空间,完全不在本次名字查找的范围内,导致检查表达式被判定为非法,最终该转发模板被SFINAE机制排除。
而如果将代码改为std::cout << res则能正常编译,因为此时普通查找会从全局作用域开始,能够找到用户定义的全局重载。
内容的提问来源于stack exchange,提问作者Daniel
相关产品推荐
相关产品推荐

