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

Output operator for class template with variadic template pack

本文关键字:输出 运算符 参数 变参      更新时间:2023-10-16

我尝试编写一个模板类并为其输出运算符,如下所示:

#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拆分不是必然的。此外,由于代码极度膨胀的后果,这根本不是可取的。

重现问题的简单方法:

int main()
{
    using namespace N;
    std::cout << std::endl;
}

在这种情况下,候选函数都是来自namespace stdoperator<<重载,所有成员运算符<<来自std::ostream,而你的函数模板operator<<来自namespace N

13.3.1/7:"如果候选函数是函数模板,则使用模板参数推导生成候选函数模板专用化">

因此,在开始解决重载之前,必须从std::endl推导出X<types...> const&,这是模板函数的地址。函数的地址是函数指针类型,将N::X<types...> const&与指针类型匹配的唯一方法是将types...推断为空列表。

(替换当然会失败,因为没有从任何函数指针类型到 N::X<> 的隐式转换,这会悄悄地消除过载,因为不可行,但静态断言不在直接上下文中,是一个硬错误(

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