Реализация 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 &¶m){
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> &¶m)
{
return static_cast<T&&>(param);
}
template< class T >
T&& forward( std::remove_reference_t<T> ¶m)
{
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 &¶m){
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&& &¶m) will result int&& forward(int&& param)
<int&> int& && forward(int& &¶m) will result int& forward(int& param)
<int> int && forward(int &¶m) will result int&& forward(int&& param)
Обратите внимание, что вторая функция: T&& forward (std:: remove_reference_t ¶m) заполняет только то, что не хватало
<int> int && forward(int ¶m) 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
, опуская параметр подробного типа. Это прекрасное произведение искусства.
С уважением, ФМ.