Подтвердить что ты не робот

Идеальный проход

Я думаю о проблеме, которая имеет некоторое сходство с совершенной пересылкой, но где аргумент функции не передается вызываемой функции, а возвращается. Вот почему я называю это "совершенным прохождением".

Проблема заключается в следующем:

Скажем, у нас есть функция, которая берет объект по ссылке (и, возможно, некоторые дополнительные аргументы), модифицирует этот объект и возвращает измененный объект. Наиболее известным примером таких функций являются, вероятно, operator<< и operator>> для iostreams.

Позвольте использовать iostreams в качестве примера, потому что он позволяет хорошо показать, что мне нужно. Например, одна вещь, которую иногда хотелось бы сделать, это:

std::string s = (std::ostringstream() << foo << bar << baz).str();

Конечно, это не работает по двум причинам:

  • std::ostringstream() является rvalue, но operator<< принимает значение lvalue как первый аргумент

  • operator<< возвращает ostream& (ну, по крайней мере, для стандартных фактически a basic_ostream<CharT, Traits>&, где CharT и Traits выводятся из первого аргумента).

Итак, предположим, что мы хотим создать оператор вставки, чтобы работа над ним (вы, очевидно, не можете сделать это для существующих операторов, но вы можете сделать это для своих собственных классов). Очевидно, что решение должно иметь следующие черты:

  • Первый аргумент может принимать либо lvalue, либо rvalue.

  • Тип возврата должен быть того же типа, что и переданный. Но, конечно, он должен принимать только те потоки (т.е. классы, полученные из экземпляра basic_ostream).

В этом конкретном случае использования не требуется, я хочу добавить третье требование:

  • Если первым аргументом является rvalue, так же как и возвращаемое значение, в противном случае возвращается lvalue.

Это дополнительное правило состоит в том, что вы можете перемещать-построить из rvalue, переданного через функцию (я не знаю, если потоки С++ 11 перемещаются конструктивно, но предполагается, что это более общая схема, поток как удобный пример).

Очевидно, что в С++ 03 эти требования не могут быть выполнены. Однако в С++ 11 у нас есть ссылки rvalue, которые должны сделать это возможным.

Здесь моя попытка:

#include <iostream>
#include <sstream>
#include <string>

template<typename Ostream> struct is_ostream
{
  typedef typename std::remove_reference<Ostream>::type candidate;
  typedef typename candidate::char_type char_type;
  typedef typename candidate::traits_type traits_type;
  typedef std::basic_ostream<char_type, traits_type> basic_ostream;
  static const bool value = std::is_base_of<basic_ostream, candidate>::value;
};

class SomeType {};

template<typename Ostream>
 typename std::enable_if<is_ostream<Ostream>::value, Ostream&&>::type
  operator<<(Ostream&& is, SomeType const& x)
{
  is << "SomeType";
  return std::forward<Ostream>(is);
}

int main()
{
  SomeType t;

  std::string s = (std::ostringstream() << t).str();

  std::cout << s << std::endl;
}

Он действительно компилируется с помощью gcc (с использованием опции -std=c++0x) и при запуске выдает SomeType, как ожидалось. Тем не менее, это довольно сложный механизм, чтобы добраться туда.

Поэтому мои вопросы:

  • Является ли оператором вывода, как я его написал, действительно работает как ожидалось (и выполняя все требования, которые я дал)? Или это может произойти неожиданным образом или иметь другие неожиданные последствия? Особенно: Я использую std::forward значение здесь?

  • Есть ли более простой способ удовлетворить требования?

  • Предполагая, что это действительно правильно и самый простой способ сделать это: считаете ли вы, что это хорошая идея, или вы бы посоветовали ему (как специально для потокового вывода, так и в моем примере, и как более общая схема передачи объектов через функции)?

4b9b3361

Ответ 1

std::ostringstream() - значение r, но operator<< принимает значение lvalue как первый аргумент

Существует общий механизм ввода rvalue-потоков, но он возвращает basic_ostream<charT, traits>&:

template <class charT, class traits, class T>
  basic_ostream<charT, traits>&
  operator<<(basic_ostream<charT, traits>&& os, const T& x);

Чтобы правильно работать для вашего примера, ему пришлось бы возвращать производный тип потока (std::ostringstram).

  • Является ли оператором вывода, как я его написал, действительно работает как ожидалось (и выполняя все требования, которые я дал)? Или это может неожиданными способами или иметь другие неожиданные последствия? Особенно: мое использование std::forward здесь верно?

Ваш код выглядит корректно для меня.

  • Есть ли более простой способ удовлетворить требования?

Ваш код похож на код, который я написал для решения этой проблемы (см. ниже).

  • Предполагая, что это действительно правильно и самый простой способ сделать это: считаете ли вы, что это хорошая идея, или вы бы посоветовали ему (как специально для потока, как в моем примере, так и в качестве более общая схема передачи объектов через функции)?

Идиома выглядит хорошо для меня.

В libС++ Я определил вставку rvalue ostream, как показано ниже (в качестве расширения). Я сделал попытку добиться его стандартизации, но было уже поздно, и комитет, по понятным причинам, не в настроении для большего обмана:

template <class _Stream, class _Tp>
inline
typename enable_if
<
    !is_lvalue_reference<_Stream>::value &&
    is_base_of<ios_base, _Stream>::value,
    _Stream&&
>::type
operator<<(_Stream&& __os, const _Tp& __x)
{
    __os << __x;
    return std::move(__os);
}

В отличие от вашего, этот только принимает rvalue-потоки. Но, как и ваш, он возвращает конкретный производный тип и поэтому работает с вашим примером.