Вопрос

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.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top