Std:: forward без безупречной пересылки?

Совет по std::forward обычно ограничивается каноническим вариантом использования аргументов шаблона идеальной пересылки; некоторые комментаторы заходят так далеко, что говорят, что это единственное допустимое использование std::forward. Но рассмотрите такой код:

// Temporarily holds a value of type T, which may be a reference or ordinary
// copyable/movable value.
template <typename T>
class ValueHolder {
 public:
  ValueHolder(T value)
    : value_(std::forward<T>(value)) {
  }


  T Release() {
    T result = std::forward<T>(value_);
    delete this;
    return result;
  }

 private:
  ~ValueHolder() {}

  T value_;
};

В этом случае проблема идеальной пересылки не возникает: поскольку это шаблон шаблона, а не шаблон функции, клиентский код должен явно указывать T и может выбрать, как и как его можно отфильтровать. Точно так же аргумент std::forward не является "универсальной ссылкой".

Тем не менее, std::forward выглядит здесь очень хорошо: мы не можем просто оставить его, потому что он не будет работать, если T - тип только для перемещения, и мы не можем использовать std::move потому что он не работает, если T является ссылочным типом lvalue. Конечно, мы могли бы частично специализировать ValueHolder использовать прямую инициализацию для ссылок и std::move для значений, но это кажется чрезмерной сложностью, когда std::forward выполняет задание. Это также кажется разумным концептуальным соответствием для значения std::forward: мы пытаемся в целом перенаправить что-то, что может быть или не быть ссылкой, только мы перенаправляем его вызывающей функции, а не мы называем себя.

Это звуковое использование std::forward? Есть ли причина избежать этого? Если да, то какая предпочтительная альтернатива?

Ответ 1

std::forward является условным move -cast (или более технически rvalue cast), не более, не что иное. Хотя использование его вне идеального контекста пересылки запутывает, оно может делать правильные вещи, если применяется те же условия в move.

У меня возникло бы желание использовать другое имя, даже если только тонкая обертка на forward, но я не могу придумать хороший вариант для вышеупомянутого случая.

Альтернативно сделайте условный переход более явным. Т.е.,

template<bool do_move, typename T>
struct helper {
  auto operator()(T&& t) const
  -> decltype(std::move(t))
  {
      return (std::move(t));
  }
};
template<typename T>
struct helper<false, T> {
  T& operator()(T&& t) const { return t; }
};
template<bool do_move, typename T>
auto conditional_move(T&& t)
 ->decltype( helper<do_move,T>()(std::forward<T>(t)) )
{
    return ( helper<do_move,T>()(std::forward<T>(t)) );
}

то есть noop pass-through, если bool - false, а move, если true. Затем вы можете сделать свой эквивалент-to-std::forward более явным и менее зависимым от понимания пользователем тайных секретов С++ 11 для понимания вашего кода.

Использование:

std::unique_ptr<int> foo;
std::unique_ptr<int> bar = conditional_move<true>(foo); // compiles
std::unique_ptr<int> baz = conditional_move<false>(foo); // does not compile

С третьей стороны, то, что вы делаете выше, требует разумного глубокого понимания семантики rvalue и lvalue, поэтому, возможно, forward безвреден.

Ответ 2

Этот вид обертки работает до тех пор, пока он правильно используется. std::bind делает что-то подобное. Но этот класс также отражает, что это функция с одним выстрелом, когда геттер выполняет ход.

ValueHolder является неправильным, потому что он явно поддерживает ссылки, которые противоположны значениям. Подход std::bind заключается в том, чтобы игнорировать lvalue-ness и применять семантику значения. Чтобы получить ссылку, пользователь применяет std::ref. Таким образом, ссылки моделируются единым смысловым смысловым интерфейсом. Я использовал собственный одноклассный rref класс с bind.

В данной реализации есть несколько несоответствий. return result; завершится сбой в специализированной задаче rvalue, поскольку имя result является значением lvalue. Вам нужен другой forward. Также const -qualified версия Release, которая удаляет себя и возвращает копию сохраненного значения, будет поддерживать случайный угловой случай объекта, сконструированного с учетом констант, но динамически распределенного. (Да, вы можете delete a const *.)

Кроме того, будьте осторожны, что пустой ссылочный элемент отображает класс, не назначаемый. std::reference_wrapper тоже работает вокруг.

Как и для использования forward как такового, он следует своей рекламируемой цели, если он передает ссылку на аргумент в соответствии с типом, выведенным для исходного исходного объекта. Это предполагает дополнительную работу ног, предположительно, в классах, которые не показаны. Пользователь не должен писать явный аргумент шаблона... но в конце он субъективен, что хорошо, просто приемлемо или хакерски, если нет ошибок.