Почему 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.