Перегрузка на ostream в шаблонной функции - PullRequest
3 голосов
/ 14 мая 2011

У меня есть переменная функция, которую я хочу перегрузить для первого типа параметра.

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 Ответ

5 голосов
/ 14 мая 2011

Это потому, что ostringstream требуется базовое преобразование в ostream, когда вы передаете его в другой шаблон, в то время как оно не нуждается ни в каком преобразовании, когда вы передаете его в шаблон, который перенаправляет в write(std::cout, ...).Поэтому, если вы передаете ostringstream, он выбирает более общий шаблон, который перенаправляет поток ostring в качестве аргумента для вывода в более конкретный шаблон.Вывод 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)... );
}

Таким образом, если вы вызовете его с чем-то отличным от lvalue ostream в качестве первого аргумента, он вызовет write_dispatch, что преобразует вызов в такойlvalue ostream, так что ваш другой шаблон write может продолжаться.

В заключительной ноте вы должны сказать out << std::forward<Head>(head), иначе вся ваша работа по использованию std::forward на предыдущих этапах рекурсии будетдля нуля, потому что в конце концов вы бы все равно вывели как lvalues.

...