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

Существуют ли какие-либо варианты использования std:: forward с prvalue?

Наиболее распространенное использование std::forward заключается в том, чтобы идеально переадресовать перенаправление (универсальную) ссылку, например

template<typename T>
void f(T&& param)
{
    g(std::forward<T>(param)); // perfect forward to g
}

Здесь param - это lvalue, а std::forward заканчивается тем, что он присваивает значение rvalue или lvalue, в зависимости от того, что ограничивал его аргумент.

Глядя на определение std::forward на cppreference.com Я вижу, что существует также rvalue перегрузка

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

Может ли кто-нибудь объяснить мне причину перегрузки rvalue? Я не вижу никакого варианта использования. Если вы хотите передать значение r функции, вы можете просто передать его как есть, не нужно применять std::forward на нем.

Это отличается от std::move, где я вижу, почему нужно также перегружать rvalue: вы можете иметь дело с общим кодом, в котором вы не знаете, что вам передается, и вы хотите безоговорочную поддержку для перемещения семантики, см., например, Почему std:: move принимает универсальную ссылку?.

EDIT Чтобы прояснить вопрос, я спрашиваю, почему перегрузка (2) здесь необходима, и прецедент для него.

4b9b3361

Ответ 1

Хорошо, так как @vsoftco попросила краткую версию использования здесь уточненную версию (используя его идею иметь "my_forward", чтобы на самом деле увидеть, как вызвана перегрузка).

Я интерпретирую "пример использования", предоставляя образец кода, который без prvalue не компилируется или ведет себя по-другому (независимо от того, что было бы действительно полезно или нет).

У нас 2 перегрузки для std::forward

#include <iostream>

template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type& t) noexcept
{
    std::cout<<"overload 1"<<std::endl;
    return static_cast<T&&>(t);
}

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

И у нас есть 4 возможных варианта использования

Использовать случай 1

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &&
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(v)); // &
    return 0;
}

Использовать случай 2

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &&
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(std::move(v))); //&&
    return 0;
}

Пример использования 3

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &
    Library( vector<int> a):b(a){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(v)); // &
    return 0;
}

Случай использования 4

#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // &
    Library( vector<int> a):b(a){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    Library a( my_forward<vector<int>>(std::move(v))); //&&
    return 0;
}

Здесь резюме

  • Используется перегрузка 1, без нее вы получаете ошибку компиляции
  • Используется перегрузка 2, без нее вы получаете ошибку компиляции
  • Используется перегрузка 1, с которой вы получаете ошибку компиляции.
  • Используется перегрузка 2, без нее вы получаете ошибку компиляции

Обратите внимание, что если мы не используем forward

Library a( std::move(v));
//and
Library a( v);

вы получаете:

  • Ошибка компиляции
  • Compile
  • Compile
  • Compile

Как вы видите, если вы используете только одну из двух перегрузок forward, вы в основном вынуждаете не компилировать 2 из 4 случаев, а если вы не используете forward вообще, вы можете скомпилировать только 3 из 4 случаев.

Ответ 2

Этот ответ предназначен для ответа на комментарий @vsoftco

@DarioOO благодарит за ссылку. Можете ли вы написать краткий ответ? Из вашего примера мне все еще не ясно, почему std:: forward необходимо также определить для rvalues ​​

Короче:

Поскольку без специализации rvalue следующий код не будет компилировать

#include <utility>
#include <vector>
using namespace std;

class Library
{
    vector<int> b;
public:
    // hi! only rvalue here :)
    Library( vector<int>&& a):b(std::move(a)){

    }
};

int main() 
{
    vector<int> v;
    v.push_back(1);
    A a( forward<vector<int>>(v));
    return 0;
}

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

Длинная версия:

Вам нужно переместить v, потому что класс Library не имеет конструктора, принимающего значение lvalue, а только ссылки rvalue. Без совершенной пересылки мы оказались бы в нежелательном поведении:

функции обертывания будут приводить к высокой эффективности при передаче тяжелых объектов.

с семантикой перемещения, мы убеждаемся, что конструктор перемещения используется IF ВОЗМОЖНО. В приведенном выше примере, если мы удалим std::forward, код не будет компилироваться.

Итак, что на самом деле делает forward? перемещение элемента без нашего консенсуса? Неа!

Он просто создает копию вектора и перемещает его. Как мы можем быть уверены в этом? Просто попробуйте обратиться к элементу.

vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v)); //what happens here? make a copy and move
std::cout<<v[0];     // OK! std::forward just "adapted" our vector

если вы переместите этот элемент

vector<int> v;
v.push_back(1);
A a( std::move(v)); //what happens here? just moved
std::cout<<v[0];  // OUCH! out of bounds exception

Чтобы перегрузка была необходима для обеспечения неявного преобразования, которое по-прежнему безопасно, но не возможно без перегрузки.

Infact, следующий код просто не компилируется:

vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor

Реальный прецедент:

Вы можете утверждать, что аргументы пересылки могут создавать бесполезные копии и, следовательно, скрыть возможное поражение производительности, да, это действительно так, но рассмотрите реальные варианты использования:

template< typename Impl, typename... SmartPointers>
static std::shared_ptr<void> 
    instancesFactoryFunction( priv::Context * ctx){
        return std::static_pointer_cast<void>( std::make_shared<Impl>(

                std::forward< typename SmartPointers::pointerType>( 
            SmartPointers::resolve(ctx))... 
            )           );
}

Код был взят из моей рамки (строка 80): Infectorpp 2

В этом случае аргументы пересылаются из вызова функции. SmartPointers::resolve возвращаемые значения корректно перемещаются независимо от того, что конструктор Impl принимает значение rvalue или lvalue (так что ошибки компиляции и все равно не перемещаются).

В принципе вы можете использовать std::foward в любом случае, в котором вы хотите сделать код более простым и понятным, но вы должны иметь в виду 2 балла

  • дополнительное время компиляции (не так много на самом деле)
  • может вызывать нежелательные копии (когда вы явно не перемещаете что-то во что-то, что требует rvalue)

Если использовать с осторожностью, это мощный инструмент.

Ответ 3

Я смотрел на этот вопрос раньше, прочитал ссылку Ховарда Хиннанта, не мог полностью заглянуть в него после часа размышлений. Теперь я смотрел и получил ответ через пять минут. (Edit: получил ответ слишком щедрым, так как ссылка Hinnant имела ответ. Я имел в виду, что я понял и смог объяснить это более простым способом, который, надеюсь, кто-то найдет полезным).

В принципе, это позволяет вам быть общим в определенных ситуациях, в зависимости от введенного кода. Рассмотрим этот код:

#include <utility>
#include <vector>
#include <iostream>
using namespace std;

class GoodBye
{
  double b;
 public:
  GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
  GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};

struct Hello {
  double m_x;

  double & get()  { return m_x; }
};

int main()
{
  Hello h;
  GoodBye a(std::forward<double>(std::move(h).get()));
  return 0;
}

Этот код печатает "move". Интересно, что если я удаляю std::forward, он печатает копию. Это, для меня, трудно оборачивать мой разум, но позвольте принять его и двигаться дальше. (Edit: Я предполагаю, что это происходит потому, что get вернет ссылку lvalue на rvalue. Такая сущность распадается на lvalue, но std:: forward будет использовать ее в rvalue, как и при обычном использовании переадресации. хотя).

Теперь представьте себе другой класс:

struct Hello2 {
  double m_x;

  double & get() & { return m_x; }
  double && get() && { return std::move(m_x); }
};

Предположим, что в коде в main h был экземпляр Hello2. Теперь нам больше не нужен std:: forward, потому что вызов std::move(h).get() возвращает rvalue. Однако предположим, что код является общим:

template <class T>
void func(T && h) {
  GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}

Теперь, когда мы вызываем func, мы хотели бы, чтобы он корректно работал как с Hello, так и с Hello2, т.е. мы хотели бы вызвать движение. Это происходит только для rvalue Hello, если мы включаем внешний std::forward, поэтому нам это нужно. Но... Мы добрались до пергамента. Когда мы передаем rvalue Hello2 этой функции, перегрузка rvalue из get() уже вернет значение rvalue double, поэтому std::forward фактически принимает значение r. Поэтому, если это не так, вы не сможете писать полностью общий код, как указано выше.

Блин.