简体   繁体   中英

Overload on ostream in a variadic template function

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM