Почему std:: forward имеет две перегрузки?
Учитывая следующие правила сворачивания ссылок
-
T& &
→ T&
-
T&& &
→ T&
-
T& &&
→ T&
-
T&& &&
→ T&&
Из третьего и четвертого правил следует, что T(ref qualifer) &&
является тождественным преобразованием, т.е. T&
остается при T&
и T&&
остается при T&&
. Почему у нас есть две перегрузки для std::forward
? Не может ли следующее определение служить всем целям?
template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& forward(const typename std::remove_reference<T>::type& val) {
return static_cast<T&&>(const_cast<T&&>(val));
}
Здесь единственной целью служит const std::remove_reference<T>&
не делать копии. И enable_if
помогает гарантировать, что функция вызывается только для значений non const. Я не совсем уверен, нужен ли const_cast
, так как это не сама ссылка, которая const.
Так как forward
всегда вызывается с явными параметрами шаблона, нам нужно рассмотреть два случая:
-
forward<Type&>(val)
Здесь тип T
в forward
будет T&
, поэтому возвращаемый тип будет преобразованием идентичности в T&
-
forward<Type&&>(val)
Здесь тип T
в forward
будет T&&
, поэтому возвращаемый тип будет тождественным преобразованием в T&&
Итак, зачем нам нужны две перегрузки, как описано в http://en.cppreference.com/w/cpp/utility/forward?
Примечание. Я не уверен, что std::forward
когда-либо используется с типами const
, но я отключил forward
в этом случае, потому что я никогда не видел, чтобы он использовался так. Также перемещение семантики не имеет смысла и в этом случае.
Ответы
Ответ 1
Хорошим местом для начала будет Howard Hinnant answer и paper on std::forward()
.
Ваша реализация правильно обрабатывает все обычные прецеденты (T& --> T&
, T const& --> T const&
и T&& --> T&&
). То, что он не может обработать, - это обычные и простые в использовании ошибки, которые очень сложно отлаживать в вашей реализации, но не скомпилировать с помощью std::forward()
.
Учитывая эти определения:
struct Object { };
template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& my_forward(const typename std::remove_reference<T>::type& val) {
return static_cast<T&&>(const_cast<T&&>(val));
}
template <class T>
void foo(T&& ) { }
Я могу передать не const
ссылки на объекты const
, как многообразие lvalue:
const Object o{};
foo(my_forward<Object&>(o)); // ok?? calls foo<Object&>
foo(std::forward<Object&>(o)); // error
и многообразие r:
const Object o{};
foo(my_forward<Object>(o)); // ok?? calls foo<Object>
foo(std::forward<Object>(o)); // error
Я могу передавать ссылки lvalue на rvalues:
foo(my_forward<Object&>(Object{})); // ok?? calls foo<Object&>
foo(std::forward<Object&>(Object{})); // error
Первые два случая приводят к потенциально модификации объектов, которые должны были быть const
(которые могли бы быть UB, если они были сконструированы const
), последний случай передает ссылку на обвязку lvalue.