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

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

13 голосов
/ 12 января 2012

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

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

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

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

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

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

  • std::ostringstream()является r-значением, но operator<< принимает l-значение в качестве первого аргумента.

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

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

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

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

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

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

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

Очевидно, что в C ++ 03 все эти требования не могут быть выполнены.Однако в C ++ 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 как положено.Тем не менее, это довольно сложный механизм, чтобы добраться туда.

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

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

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

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

1 Ответ

6 голосов
/ 12 января 2012

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

Существует универсальный инсертор для получения потоков 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).

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

Ваш код мне кажется правильным.

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

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

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

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

В libc ++ я определил инвертор 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 потоки. Но, как и ваш, он возвращает конкретный производный тип и работает с вашим примером.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...