Реализация std::forward

Я читаю Обзор нового C++ (C++11/14) (только PDF), на слайде 288 он дает реализацию std::forward:

template<typename T>                // For lvalues (T is T&),
T&& std::forward(T&& param)         // take/return lvalue refs.
{                                   // For rvalues (T is T),
    return static_cast<T&&>(param); // take/return rvalue refs.
}

А затем дает еще одну реализацию в тексте:

Обычная реализация std::forward:

template<typename T>
struct identity {
    typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{ return static_cast<identity<T>::type&&>(param); }

В чем разница? Почему последняя обычная реализация?

5 ответов

Решение

Проблема с первым заключается в том, что вы можете написать std::forward(x), который не делает то, что вы хотите, так как он всегда производит lvalue ссылки.

Аргумент во втором случае - это не выводимый контекст, препятствующий автоматическому выводу аргумента шаблона. Это заставляет вас писать std::forward<T>(x), что правильно делать.

Кроме того, тип аргумента для второй перегрузки должен быть typename identity<T>::type& потому что вклад в идиоматическое использование std::forward всегда lvalue.

Редактировать: Стандарт фактически обязывает подпись, эквивалентную этой (что, кстати, именно то, что имеет libC++):

template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;

Реализация в libC++ использует std::remove_reference и две перегрузки. Вот источник (после удаления некоторых макросов):

template <class T>
inline T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
    return static_cast<T&&>(t);
}

template <class T>
inline T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
    static_assert(!std::is_lvalue_reference<T>::value,
                  "Can not forward an rvalue as an lvalue.");
    return static_cast<T&&>(t);
}

но обратите внимание, что в C++14, std::forward является constexpr,

Первый случай, как сказал Себастьян Редл, всегда даст вам ссылку на lvalue. Причина в том, что ссылка на rvalue в параметре будет передана как ссылка на lvalue, а параметр T&& Тип - это универсальная ссылка, а не ссылка на значение.

На самом деле, если первый случай верен, нам даже не нужно forward больше Вот эксперимент, чтобы продемонстрировать, как передаются универсальные эталонные параметры

template <typename T, typename U>
void g(T&& t, U&& u)
{
    std::cout << "t is lvalue ref: "
              << std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
    std::cout << "t is rvalue ref: "
              << std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
    std::cout << "u is lvalue ref: "
              << std::is_lvalue_reference<decltype(u)>::value << std::endl; // 1
    std::cout << "u is rvalue ref: "
              << std::is_rvalue_reference<decltype(u)>::value << std::endl; // 0
}

template <typename T, typename U>
void f(T&& t, U&& u)
{
    std::cout << "t is lvalue ref: "
              << std::is_lvalue_reference<decltype(t)>::value << std::endl; // 1
    std::cout << "t is rvalue ref: "
              << std::is_rvalue_reference<decltype(t)>::value << std::endl; // 0
    std::cout << "u is lvalue ref: "
              << std::is_lvalue_reference<decltype(u)>::value << std::endl; // 0
    std::cout << "u is rvalue ref: "
              << std::is_rvalue_reference<decltype(u)>::value << std::endl; // 1

    g(t, u);
}

int main()
{
    std::unique_ptr<int> t;
    f(t, std::unique_ptr<int>());
    return 0;
}

Программа оказывается, что оба t а также u прошло от f в g это lvalue ссылки, несмотря на это u это ссылка на значение в f, Так что в первом случае параметр forward просто не имеет шансов быть эталоном.

identity используется для изменения типа параметра с универсальной ссылки на rvalue ссылку (как упомянуто Redl, более точно использовать std::remove_reference). Однако это изменение делает вывод типа шаблона более невозможным, так что параметр типа для forward обязательно, в результате напишем forward<T>(t),

Но второй случай в вашем вопросе также неверен, как также упоминал Redl, правильный подход - это перегрузка, параметр которой является ссылкой на lvalue.

Самая простая реализация, которую я могу найти, это

template <typename T>
T&& forward(typename identity<T>::type& param)
{
    return static_cast<T&&>(param);
}

Это работает для универсальных ссылок, например

template <typename T, typename U>
void f(T&& t, U&& u)
{
    ::forward<T>(t);
    ::forward<U>(u);
}

std::unique_ptr<int> t;
f(t, std::unique_ptr<int>());
// deduction in f:
//   T = unique_ptr&, decltype(t) = unique_ptr&
//   U = unique_ptr, decltype(u) = unique_ptr&& (but treated as an lvalue reference)
// specialization of forward:
//   forward<T> = forward<unique_ptr&>, param type = unique_ptr&
//                                      return type = unique_ptr&
//   forward<U> = forward<unique_ptr>,  param type = unique_ptr&
//                                      return type = unique_ptr&&

Ответы

В чем разница?

На практике нет никакой разницы, ожидаемый результат при выполнении такой же, учитывая, что только способ написания более подробный.

Вы просто определяете тип T и используете тип, определенный через typename , что означает буквально никакой разницы.

Почему последняя обычная реализация?

Я не следил за этим обновлением, поэтому не могу сказать, использовали ли они этот код официально в своем репозитории, вероятно , это чья-то реализация, а не официальная, независимо от причины:

Они решили сделать это таким образом, хотя обе реализации недопустимы для реального сценария.

Обратите внимание, что оба имеют одинаковый результат и вообще не расходятся при выполнении, то есть в реальном сценарии расхождение между ними не вызовет никакой разницы, ошибки запуска / компиляции или чего-либо в этом роде.

Обзор кода

Пропущено добавление "typename" для успешной компиляции

      template<typename T>
struct identity {
    typedef T type;
};
template<typename T>
T&& forward(typename identity<T>::type&& param)
{
    return static_cast<**typename** identity<T>::type&&>(param);
}

Пояснения

Почему недействительно

Учитывая приведенный ниже код, аргумент T - это «int», содержащий тип возвращаемого значения «int &&», вместе ожидающий параметр функции типа rvalue (int &&), но было передано lvalue int &, генерирующее ошибку компиляции.
      #include <iostream>

template<typename T>         
T&& forward(T&& param)    
{                                   
    return static_cast<T&&>(param);
}

int main() {
  int value = 5;
  forward<int>(value);
  return 1;
}

Реальный пейзаж

Строка 25, redir (5), вызывает ошибку компиляции, и это был бы реальный сценарий.

Ошибка заключается в том, что шаблон T, относящийся к void redir, имеет тип int и при вызове forward <int> (param) он передает param, который является переменной lvalue, которая была объяснена в разделе Почему недействительна

      #include <iostream>

template<typename T>         
T&& forward(T&& param)    
{                                   
    return static_cast<T&&>(param);
}

void print(int &&value){
  std::cout << "rvalue: " << value << std::endl; 
}

void print(int &value){
  std::cout << "lvalue: " << value << std::endl; 
}

template <class T>
void redir(T &&param){
  print(forward<T>(param));
}

int main() {
  int value = 5;
  redir(value);
  **redir(5);**
  return 0;
}

Решение

В настоящее время код обновлен, и проблема устранена, вы можете проверить это по адресу: https://en.cppreference.com/w/cpp/utility/forward.

Код будет похож на этот:

      #include <iostream>

template< class T >
T&& forward( std::remove_reference_t<T> &&param)
{                                   
  return static_cast<T&&>(param);
}

template< class T >
T&& forward( std::remove_reference_t<T> &param)
{                                   
  return static_cast<T&&>(param);
}

void print(int &&value){
  std::cout << "rvalue: " << value << std::endl; 
}

void print(int &value){
  std::cout << "lvalue: " << value << std::endl; 
}

template <class T>
void redir(T &&param){
  print(forward<T>(param));
}

int main() {
  int value = 5;
  redir(value);
  redir(5);
  return 0;
}

std:: remove_reference_t не является обязательным, даже без его использования результат будет таким же.

Они решили использовать remove_reference_t из-за хорошей практики, то есть они подтверждают, что первая функция ожидает int &, а вторая int &&.

Почему std:: remove_reference не имеет значения

Хотя это не меняет ожидаемого результата при выполнении кода, это важно из-за хорошей практики программирования, так сказать, усиливает ожидаемый результат.

Что ж, но объяснение результата не меняется из-за следующих правил преобразования:

      TR   R

T&   &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&   && -> T&  // rvalue reference to cv TR -> TR (lvalue reference to T)
T&&  &  -> T&  // lvalue reference to cv TR -> lvalue reference to T
T&&  && -> T&& // rvalue reference to cv TR -> TR (rvalue reference to T)

Насколько мы можем, единственный способ получить значение r (T&&) - это преобразование T&& + T&& или только T&&.

Почему необходимо использовать 2 функции (std:: forward), а не только static_cast в одной функции

      <int&&> int&& && forward(int&& &&param) will result int&& forward(int&& param)
<int&> int& && forward(int& &&param) will result int& forward(int& param)
<int> int && forward(int &&param) will result int&& forward(int&& param)

Обратите внимание, что вторая функция: T&& forward (std:: remove_reference_t &param) заполняет только то, что не хватало

      <int> int && forward(int &param) will result int&& forward(int& param)

По этой причине вам нужно объявить 2 функции std:: forward.

Согласно справочному предложению rvalue, именованное rvalue ничем не отличается от lvalue, за исключением . То есть любой именованный параметр функции не может быть неявно приведен или использован для инициализации другой ссылки rvalue; он копирует только ссылки lvalue; но может явно указать значение ссылки. Поэтому, когда вы передаете именованный (в отличие от безымянного временного) объект/переменную в функцию, он может быть захвачен только lvlaues. Чтобы привести аргумент функции к rvalue a static_castнеобходим, но он слишком многословен о значении его выходного типа. Любая функция, такая как не может вывести decltypeего именованного операнда. Таким образом, тип должен быть подробно передан как аргумент типа шаблона; Правильный вывод возвращаемого типа forwardзависит от своего аргумента типа. И необходимы две перегрузки этой специальной функции: перегрузка lvalue захватывает именованные rvalue, а перегрузка rvalue захватывает неименованные lvalue, которые не могут напрямую привязываться к ссылкам lvalue. Вывод базового типа входной ссылки предотвращается использованием алисов типов шаблонов и подчеркивается их вложением в тип свойств; либо std::type_identityили же std::remove_referenceили еще. Это предохраняет программистов от ловушки непреднамеренной передачи неправильного значения при использовании . Мне еще предстоит увидеть трюк, который может достичь функциональности std::forward, опуская параметр подробного типа. Это прекрасное произведение искусства.

С уважением, ФМ.

Другие вопросы по тегам