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

发布于 2024-11-07 07:16:45 字数 894 浏览 4 评论 0原文

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

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

I have a variadic function that I want to overload on the first parameter type.

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)... );
}

But these functions don't behave as expected.

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!

What's going on here?
Why isn't overload resolution picking the function I want?
Is there a way to do this without lots of metaprogramming? (I was able to work around it with std::is_convertible but the solution was much larger than the simple code I've shown above).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

冷血 2024-11-14 07:16:45

这是因为,当您将 ostringstream 传递给其他模板时,需要将其基本转换为 ostream,而当您将其传递给转发到的模板时,它不需要任何转换。 write(std::cout, ...)。因此,如果您传递 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),否则您在前面的递归步骤中使用 std::forward 的所有工作都将毫无意义,因为最终您会将所有内容输出为无论如何,左值。

That's because ostringstream needs a base conversion to ostream when you pass it to the other template, while it doesn't need any conversion when you pass it to the template that forwards to write(std::cout, ...). So if you pass ostringstream, it selects the more generic template which forwards the ostringstream as an argument to output to the more specific template. Outputting the ostringstream converts it to a void*, which is then printed.

You can solve this with is_base_of (just feels better to me than using 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)... );
}

I personally dislike using too much SFINAE in my code, because I can't cope with a certain level of angle brackets. So I like to use overloading

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)... );
}

This way, if you call it with something other than lvalue of ostream as first argument, it will call write_dispatch, which will transform the call into such an lvalue of ostream, so that your other write template can continue.

On a final note, you should say out << std::forward<Head>(head), otherwise all your work to use std::forward in previous recursion steps would be for naught, because in the end you would output everything as lvalues anyway.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文