Как 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&) перегрузка вместо этого.

Live демо


у Говарда уже есть хороший ответ на ваш вопрос о том, почему перегрузка 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

Live demo


как 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 форвардные(универсальные) опорные параметры.Правила следующие:

  1. если f expr аргумента является lvalue ,оба x и T будет ссылка lvalue тип
  2. если 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, чтобы решить, двигаться или нет.