3

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

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

4

1 回答 1

5

那是因为当你将它传递给另一个模板时ostringstream需要一个基本转换ostream,而当你将它传递给转发到的模板时它不需要任何转换write(std::cout, ...)。因此,如果您通过ostringstream,它将选择更通用的模板,该模板将 ostringstream 作为参数转发到更具体的模板。输出ostringstream将其转换为 a 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将是徒劳的,因为最终你无论如何都会将所有内容输出为左值。

于 2011-05-13T20:34:03.257 回答