Как std:: forward получает правильный аргумент?
считаем:
void g(int&);
void g(int&&);
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
int main()
{
f(10);
}
начиная с id-выражения x
является lvalue, и std::forward
имеет перегрузки для lvalues и rvalues, почему вызов не привязывается к перегрузке std::forward
что занимает lvalue?
template<class T>
constexpr T&& forward(std::remove_reference_t<T>& t) noexcept;
3 ответов
это тут привязка к перегрузке std::forward
принимая lvalue:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
он связывается с T == int
. Эта функция указывается для возврата:
static_cast<T&&>(t)
потому что T
на f
вывод int
. Таким образом, эта перегрузка бросает lvalue int
для xvalue с:
static_cast<int&&>(t)
вызов g(int&&)
перегрузка.
в общем, перегрузка lvalue std::forward
может привести свой аргумент либо lvalue или rvalue, в зависимости от типа T
это называется С.
перегрузка rvalue std::forward
может только бросить rvalue. Если вы попытаетесь вызвать эту перегрузку и привести к lvalue, программа плохо сформирована (требуется Ошибка времени компиляции).
так перегрузка 1:
template <class T>
constexpr T&& forward(remove_reference_t<T>& t) noexcept;
ловит lvalues.
перегрузка 2:
template <class T> constexpr T&& forward(remove_reference_t<T>&& t) noexcept;
ловит rvalues (который является xvalues и prvalues).
перегрузка 1 может бросить своего значения аргумент для lvalue или xvalue (последний, который будет интерпретироваться как rvalue для целей разрешения перегрузки).
перегрузка 2 может привести свой аргумент rvalue только к xvalue (который будет интерпретироваться как rvalue для целей разрешения перегрузки).
перегрузка 2 для случая с надписью "B. должен переслать rvalue как rvalue" в N2951. В двух словах этот случай позволяет:
std::forward<T>(u.get());
где вы не знаете, если u.get()
возвращает lvalue или rvalue, но в любом случае, если T
не является ссылочным типом lvalue, вы хотите переместить возвращаемое значение. Но вы не используете std::move
потому что если T
и ссылочный тип lvalue, вы не хотите перейти от возврата.
я знаю, что это звучит немного надуманно. Однако N2951 пошел на значительные проблемы, чтобы настроить мотивация варианты использования для how std::forward
должны вести себя со всеми комбинациями явно заданного параметра шаблона и неявно заданной категории выражений обычного параметра.
это не простое чтение, но обоснование для каждой комбинации шаблона и обычных параметров для std::forward
находится в N2951. В то время это было спорно на комитете, и продать его было непросто.
окончательная форма std::forward
- это не совсем то, что N2951 предложил. Однако это тут пропуск все шесть тестов представлены в N2951.
почему вызов не привязывается к перегрузке
std::forward
что занимает lvalue?
он делает именно это, но std::forward
не вывести его аргумент шаблона, вы говорите ему, какой это тип, и вот где происходит магия. Вы передаете prvalue f()
так f()
выводит [T = int]
. Затем он вызывает перегрузку lvalue forward
, и из-за ссылка рушатся оба типа возврата и static_cast<T&&>
это произойдет внутри forward
будет типа int&&
, таким образом вызывая void g(int&&)
перегрузка.
если вы должны были передать lvalue в f()
int x = 0;
f(x);
f()
выводит [T = int&]
, снова та же перегрузка lvalue forward
вызывается, но на этот раз тип возврата и static_cast<T&&>
как int&
, опять же из-за ссылки рушатся правила. Затем это вызовет void g(int&)
перегрузка вместо этого.
у Говарда уже есть хороший ответ на ваш вопрос о том, почему перегрузка rvalue , но я добавлю пример, который показывает две версии в действии.
основная идея двух перегрузок заключается в том, что перегрузка rvalue будет вызываться в случаях, когда результат выражения, которое вы передаете forward
дает значение rvalue (prvalue или xvalue).
скажем, у вас есть типа foo
что есть ref-квалифицированная пара get()
перегрузка функции-члена. С &&
классификатор возвращает int
в то время как другой возвращает int&
.
struct foo
{
int i = 42;
int get() && { return i; }
int& get() & { return i; }
};
и сказал f()
вызывает get()
функция-член на все, что было передано ему, и вперед, что на g()
template<class T>
auto f(T&& t)
{
std::cout << __PRETTY_FUNCTION__ << '\n';
g(forward<decltype(forward<T>(t).get())>(forward<T>(t).get()));
}
foo foo1;
f(foo1); // calls lvalue overload of forward for both calls to forward
f(std::move(foo1)); // calls lvalue overload of forward for forward<T>(t)
// but calls rvalue overload for outer call to forward
как std:: forward получает правильный аргумент?
для идеальной пересылки, как ваш код:
template<class T>
void f(T&& x)
{
g(std::forward<T>(x));
}
обратите внимание: std:: forward требует как аргумента функции, так и аргумента типа шаблона.
параметр шаблона T будет кодировать, был ли аргумент, переданный param, lvalue или rvalue а затем вперед использовать его.
В этом случае не имеет значения x
см., Что ,x
сам по себе является lvalue, перегрузка выбора 1 и x форвардные(универсальные) опорные параметры.Правила следующие:
- если
f
expr аргумента является lvalue ,обаx
иT
будет ссылка lvalue тип - если
f
expr аргумента является rvalue ,T
будет не ссылочным типом.
например, используйте исходный код gcc:
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); } //overload 1
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}// overload 2
- если функция пропуска f lvalue типа
string
илиstring&
,для передовая функция_Tp
будетstring&
,__t
будетstring & &
равноstring&
,_Tp&&
будетstring& &&
равнаstring&
.(ссылка рушится) - если функция пропуска f rvalue типа
string
илиstring&&
,для вперед функция_Tp
будетstring
,__t
будетstring&
,_Tp&&
будетstring&&
.
что делает функция?
- если rvalue для rvalue (перегрузка 2) или lvalue для lvalue(перегрузка 1), ничего не делать, просто возвращаться.
- если вы не осторожны или что-то еще, привести rvalue к lvalue, дает вам ошибку компиляции по
static_assert
.(Перегрузка 2) - если вы делаете lvalue для rvalue (перегрузка 1),это то же самое с std::move,но не удобство.
так же, как Скотт Мейерс сказать:
учитывая, что оба std:: move и std:: forward сводятся к броскам, единственная разница в том, что std:: move всегда бросает, в то время как std:: вперед только иногда ли, вы могли бы спросить, можем ли мы обойдитесь без std::move и просто используйте std:: forward везде. от a чисто техническая перспектива, ответ да: std:: вперед может сделать все это. std:: перемещение не требуется. Конечно, ни функция действительно необходимо, потому что мы могли бы писать слепки везде, но я надеюсь, мы согласимся, что это было бы, ну, отвратительно.
для чего перегрузка rvalue?
я не уверен, его следует использовать где тебе это действительно нужно.Такие, как Говард Хиннант говорит:
перегрузка 2 для случая с надписью " B. должен переслать rvalue как rvalue " в N2951. В двух словах этот случай позволяет:
std::forward<T>(u.get());
где вы не уверены, если u.get () возвращает значение lvalue или rvalue, но в любом случае, если T не является ссылочным типом lvalue, вы хотите переместить возвращаемое значение. Но вы не используете std:: move, потому что если T-lvalue ссылочный тип, вы не хотите перейти от возврата.
одним словом, это позволяет нам использовать тип T, чтобы решить, двигаться или нет.