Совет по 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
как такового, он следует своей рекламируемой цели, если он передает ссылку на аргумент в соответствии с типом, выведенным для исходного исходного объекта. Это предполагает дополнительную работу ног, предположительно, в классах, которые не показаны. Пользователь не должен писать явный аргумент шаблона... но в конце он субъективен, что хорошо, просто приемлемо или хакерски, если нет ошибок.