std :: forward и конструктор с неконстантным ссылочным аргументом - PullRequest
7 голосов
/ 02 декабря 2011

In Краткое введение в Rvalue References , идеальная пересылка предлагается в качестве идеального решения для пересылки rvalue 5 в конструктор с неконстантным ссылочным аргументом.

Но:

#include <memory>
#include <iostream>
#include <utility>

template <class T, class A1>
std::shared_ptr<T> factory(A1&& a1) {
   return std::shared_ptr<T>(new T(std::forward<A1>(a1)));
}

class X {
public:
    X(int& i){
        std::cout<<"X("<<i<<")\n";
    }
};


int main() {
    std::shared_ptr<X> p = factory<X>(5);
}

терпит неудачу в XCode 4.2 и G ++ 4.6.1 с no known conversion from int to int&, тогда как:

template <class T, class A1>
std::shared_ptr<T> factory(A1&& a1) {
   return std::shared_ptr<T>(new T(/*no forwarding*/a1));
}

компилируется.Что я не так понял?

Ответы [ 3 ]

5 голосов
/ 02 декабря 2011

Вы не можете привязать rvalues ​​к неконстантным lvalue ссылкам. В статье не предлагается использовать для этого идеальную пересылку, потому что это невозможно. Идеальная пересылка пересылает lvalues ​​как lvalues ​​и rvalues ​​как rvalues:

Здесь forward сохраняет lvalue / rvalue-ness аргумента, который был передан на завод. Если значение передается на завод, то Значение будет передано конструктору T с помощью форварда функция. Точно так же, если lvalue передается фабрике, это передается конструктору T как lvalue.

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

5 голосов
/ 02 декабря 2011

Идеальная пересылка предлагается в качестве идеального решения для пересылки значения r конструктору с неконстантным ссылочным аргументом.

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

Скорее, это означает, что она может пересылать ссылки rvalue как значения rvalue, так что вызывать move-constructor или конструктор / функцию, которая принимает ссылки rvalue.

Итак, вы должны попробовать это:

class X {
public:
    X(int& i){
        std::cout<<"X("<<i<<")\n";
    }

    //this is a constructor which takes rvalue references
    X(int&& i){ 
        std::cout<<"X("<<i<<")\n";
    }
};

То есть, начиная с factory, должен вызываться второй конструктор, а не тот, который вы написали.

Кстати, в этом случае конструктор не имеет особого смысла, потому что тип параметра int является фундаментальным типом.

Rvalue ссылки как тип параметра используется для определения move-constructor и move-assignment классов, которые управляют ресурсами.Если пользовательский класс не управляет ресурсом, то семантика перемещения не имеет смысла.

3 голосов
/ 02 декабря 2011

Игнорировать rvalue-ссылки на секунду и вместо этого делать вид, что это разрешено:

void modify_int(int& i)
{
    i = 1;
}

void foo(int& x)
{
    modify_int(x); // okay, modify_int references x
}

int i = 7;
foo(i); // makes i = 1

// illegal in standard C++, cannot bind a temporary to a non-const reference
foo(5); // makes the temporary integer equal to 1

Вы можете видеть, что временный объект изменяется, что совершенно нормально.Однако это связывание было сделано недопустимым в C ++, потому что оно обычно нежелательно (в конце концов, оно читается так, как будто 5 изменяется на 1.), но безопасно, потому что мы понимаем, что имеем дело со значением, которое следует считать временным:

void modify_int(int& i)
{
    i = 1;
}

void foo(int&& x)
{
    modify_int(x); // okay, modify_int references x
}

int i = 7;
foo(std::move(i)); // makes i = 1 (std::move makes it an rvalue)

// legal in C++11, temporary is bound to rvalue-reference
foo(5); // makes the temporary integer equal to 1

Обратите внимание, что в этой версии foo переход на modify_int все еще в порядке.Оказавшись внутри функции, тот факт, что это была rvalue-ссылка вместо lvalue-ссылки, не имеет значения: у нас все еще есть объект для ссылки. Пересылка используется в шаблонах для сохранения категории значений :

void test(int& i) {} // lvalue version of test
void test(int&& i) {} // rvalue version of test

template <typename T>
void foo(T&& x)
{
    // if x was an lvalue, forward does nothing;
    // if x was an rvalue, forward std::move's it 
    test(std::forward<T>(x)); 
}

int i = 7;
foo(i); // calls lvalue version of test

foo(5); // calls rvalue version of test

Ваш код без пересылки аналогичен второму фрагменту в моем ответе.Оказавшись внутри функции factory, a1 является обычным lvalue и прекрасно связывается со ссылкой на конструктор.Но при пересылке он превращается обратно в rvalue (потому что factory(5) вызывает его с rvalue), который не может привязаться к lvalue-reference, что приводит к ошибке.

...