我尝试按如下方式编写模板类和输出操作符:
#include <iostream>
namespace N
{
template< typename ...types >
struct X
{
static_assert((sizeof...(types) != 0), "zero length");
X() = default;
X(X const &) = default;
X(X &&) = default;
template< typename ...args >
//explicit // does not matter
X(args &&...) { ; }
int value = 10;
};
template< typename ...types >
std::ostream &
operator << (std::ostream & out, X< types... > const & x)
{
return out << x.value;
}
} // namespace N
int main()
{
using namespace N;
X< float > /*const*/ x; // `const` does not matter
std::cout << x << std::endl;
return 0;
}
但是static_assert
离子提出了:
main.cpp:9:5: error: static_assert failed "zero length"
static_assert((sizeof...(types) != 0), "zero length");
^ ~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:32:23: note: in instantiation of template class 'N::X<>' requested here
std::cout << x << std::endl;
^
1 error generated.
如果在全局X
中定义了类模板operator <<
和namespace
重载,则所有内容都相同。我发现,评论using namespace N;
行和替换X< float >
到N::X< float >
可以解决问题。
如何解释这种行为?原因是什么?
修改
我找到了解决方案:将operator <<
重载的模板参数变为如下:
template< typename first, typename ...rest >
std::ostream &
operator << (std::ostream & out, X< first, rest... > const & x)
{
return out << x.value;
}
分类typename ..types
并不是偶然的。而且,由于代码的极端膨胀是后果,所以根本不可取。
答案 0 :(得分:5)
重现问题的简单方法:
int main()
{
using namespace N;
std::cout << std::endl;
}
在这种情况下,候选函数都是来自operator<<
的{{1}}的所有重载,来自namespace std
的所有成员运算符&lt;&lt;来自std::ostream
的函数模板operator<<
namespace N
。
13.3.1 / 7:“候选人是一个功能模板,使用模板参数推导生成候选函数模板专业化”
因此,在开始重载解析之前,必须从X<types...> const&
推导出std::endl
,N::X<types...> const&
是模板函数的地址。函数的地址是函数指针类型,将types...
与指针类型匹配的唯一方法是将N::X<>
推导为空列表。
(当然替换失败,因为没有从任何函数指针类型到{{1}}的隐式转换,这会将过载安静地消除为不可行,但静态断言不是直接上下文并且是硬错误)
故事的寓意:使用指令是邪恶的。