在可变参数模板函数中对ostream进行重载

时间:2011-05-13 20:21:04

标签: c++ c++11 variadic-templates overload-resolution

我有一个可变参数函数,我想在第一个参数类型上重载。

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来解决它,但解决方案比我上面显示的简单代码要大得多。

1 个答案:

答案 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的所有工作都是徒劳的,因为最后你还是会把所有东西输出为左值。