具有可变参数模板包的类模板的输出运算符

时间:2014-04-29 03:49:30

标签: c++ templates c++11 operator-overloading variadic-templates

我尝试按如下方式编写模板类和输出操作符:

#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并不是偶然的。而且,由于代码的极端膨胀是后果,所以根本不可取。

1 个答案:

答案 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::endlN::X<types...> const&是模板函数的地址。函数的地址是函数指针类型,将types...与指针类型匹配的唯一方法是将N::X<>推导为空列表。

(当然替换失败,因为没有从任何函数指针类型到{{1}}的隐式转换,这会将过载安静地消除为不可行,但静态断言不是直接上下文并且是硬错误)

故事的寓意:使用指令是邪恶的。