我有一个可变参数函数,我想在第一个参数类型上重载。
void write( void ) { }
void write( std::ostream& ) { }
template< typename Head, typename... Rest >
void write( std::ostream& out, Head&& head, Rest&&... rest )
{
out << head;
write( out, std::forward<Rest>(rest)... );
}
template< typename... Args >
void write( Args&&... args )
{
write( std::cout, std::forward<Args>(args)... );
}
但这些功能的表现并不像预期的那样。
write( "## to cout ##" ); // printed to stdout as expected
write( std::cerr, "## to cerr ##" ); // printed to stderr as expected
std::ostringstream oss;
write( oss, "## to string ##" ); // error here
// '0x7fff9db8## to string ##' is printed to stdout!
这里发生了什么事?
为什么超载分辨率没有选择我想要的功能?
有没有办法在没有大量元编程的情况下做到这一点? (我能够使用std::is_convertible
来解决它,但解决方案比我上面显示的简单代码要大得多。
答案 0 :(得分:5)
这是因为当您将ostringstream
传递给另一个模板时,ostream
需要将基本转换为write(std::cout, ...)
,而当您将其传递给转发到{{1的模板时,它不需要任何转换}}。因此,如果您传递ostringstream
,它会选择更通用的模板,该模板将ostringstream转发为参数以输出到更具体的模板。输出ostringstream
会将其转换为void*
,然后打印出来。
您可以使用is_base_of
来解决这个问题(对我来说比使用is_convertible
感觉更好。)
template<typename Arg, typename... Args, typename =
typename std::enable_if<
!std::is_base_of<
std::ostream,
typename std::remove_reference<Arg>::type,
>::value>::type
>
void write(Arg&& arg, Args&&... args )
{
write( std::cout, std::forward<Arg>(arg), std::forward<Args>(args)... );
}
我个人不喜欢在我的代码中使用过多的SFINAE,因为我无法处理某个级别的尖括号。所以我喜欢使用重载
template< typename Arg, typename... Args >
void write_dispatch( std::true_type, Arg&& arg, Args&&... args )
{
std::ostream& os = arg;
write( os, std::forward<Args>(args)... );
}
template< typename Arg, typename... Args >
void write_dispatch( std::false_type, Arg&& arg, Args&&... args )
{
write( std::cout, std::forward<Arg>(arg), std::forward<Args>(args)... );
}
template< typename Arg, typename... Args >
void write( Arg&& arg, Args&&... args )
{
typedef typename std::remove_reference<Arg>::type nonref_type;
write_dispatch( std::is_base_of<std::ostream, nonref_type>(),
std::forward<Arg>(arg), std::forward<Args>(args)... );
}
这样,如果你用ostream
左值作为第一个参数调用它,它会调用write_dispatch
,这会将调用转换为ostream
的左值,这样您的其他write
模板就可以继续了。
最后一点,你应该说out << std::forward<Head>(head)
,否则你在以前的递归步骤中使用std::forward
的所有工作都是徒劳的,因为最后你还是会把所有东西输出为左值。