Подтвердить что ты не робот

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

Я читаю Обзор нового С++ (С++ 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); }

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

4b9b3361

Ответ 1

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

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

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

Изменить: Стандарт фактически предусматривает эквивалентную ему подпись (которая, между прочим, является именно тем, что есть у libС++):

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

Ответ 2

Реализация в libС++ использует 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);
}

но обратите внимание, что в С++ 14 std::forward есть constexpr.

Ответ 3

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

Собственно, если первый случай верен, нам даже не нужно 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 является ссылкой на rvalue в f. Поэтому в первом случае параметр forward просто не имеет возможности быть ссылкой rvalue.

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&&