Передача по ссылке, постоянная ссылка, rvalue-reference или постоянная rvalue-reference?

Я изучал передачу по ссылке, и вот тест, который я сделал:

#include <iostream>

using namespace std;

int i = 0;

//If this is uncommented, compiler gives ambiguous definition error.
//void paramCheck (string s) {
//  cout << ++i << ". Param is var.\n";
//}

void paramCheck (const string& s) {
    cout << ++i << ". Param is const ref.\n";
}

void paramCheck (string& s) {
    cout << ++i  << ". Param is non-const ref.\n";
}

void paramCheck (const string&& s) {
    cout << ++i  << ". Param is const rvalue-reference.\n";
}

void paramCheck (string&& s) {
    cout << ++i  << ". Param is non-const rvalue-reference.\n";
}


int main(int argc, char **argv) {
    //Function call test
    paramCheck("");

    paramCheck(string{""});

    string s3{""};
    paramCheck(s3);

    const string s4{""};
    paramCheck(s4);

    //Illegal
    //string& s{""};
    //paramCheck(s);

    const string& s5{s3};
    paramCheck(s5);

    string&& s6{""};
    paramCheck(s6);

    //Illegal
    //const string&& s{s1};
    //onstFP(s);

    //Reference test
    string a = s3;
    a = "a changed s3";
    cout << s3;

    {
    string& b = s3;
    b = "b changed after assigning s3\n";
    cout << "s3 is now " <<s3;

    b = s4;
    b = "b changed after assigning s4\n";
    cout << "s3 is now " <<s3;
    cout << "s4 is now " <<s4;
    }

    cin.get();
    return 0;
}

И вот результат:

1. Param is non-const rvalue-reference.
2. Param is non-const rvalue-reference.
3. Param is non-const ref.
4. Param is const ref.
5. Param is const ref.
6. Param is non-const ref.
s3 is now b changed after assigning s3
s3 is now b changed after assigning s4
s4 is now

Мой вопрос:

  • Если мы передаем константное выражение, оно всегда вызывает непостоянный rvalue-reference? При каких условиях он будет запускать постоянную rvalue-reference (и почему s6 не запускает ее?)

  • Почему не постоянная ссылка и постоянная ссылка rvalue являются незаконными?

  • Я ожидал, что нельзя изменить s3, но почему b во внутренней области может изменить s3? Если присваивание нового объекта s3 в b назначает новую ссылку, почему, когда я назначаю s4 ему и s3 получил изменен, а s4 пуст после?

Извините, что задал слишком много вопросов... Я увеличиваю очки, когда будут отвечать все вопросы:) Ссылка просто приносит мою путаницу из указателя на совершенно новый уровень.


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

Ответ 1

Сначала код

paramCheck(""); //constructs a temporary. temporaries bind to `string&&`
paramCheck(string{""}); //constructs a temporary. temporaries bind to `string&&`
string s3{""};
paramCheck(s3); //passes a reference to an existing string: `string&`
const string s4{""};
paramCheck(s4); //passes a reference to an existing string+const: `const string&`
//Illegal
//string& s{""}; //cannot assign a temporary to a non-const l-reference
                 //what would s refer to when the temporary "dies"?
                 //`const string&` would have worked though
//paramCheck(s); //passes a reference to an existing string+const: `const string&`
const string& s5{s3}; //s5 is s3, but with `const`. 
paramCheck(s5); //passes a reference to an existing string+const: `const string&`
string&& s6{""}; //r-references extend the life of temporaries.
paramCheck(s6); //passes a reference to an existing strong: `string&`
//const string&& s{s1}; //temporaries can be extended by `T&&` or `const T&` only.

//Reference test
string a = s3; //a is a _copy_ of s3
a = "a changed s3"; //so changing the copy doesn't effect the origional.
cout << s3; //s3 is still blank, it hasn't changed.

{
string& b = s3; //b isn't really a "reference" to `s3`".  `b` _IS_ `s3`.
b = "b changed after assigning s3\n"; //since `b` IS `s3`, this changes `s3`.
cout << "s3 is now " <<s3;

b = s4; //`b` _IS_ `s3`, so you just changed `s3` again.
b = "b changed after assigning s4\n";
cout << "s3 is now " <<s3;
cout << "s4 is now " <<s4; //s4 is still blank, it hasn't changed.
}

Тогда вопросы:

Если мы передаем константное выражение, оно всегда вызывает непостоянный rvalue-reference? При каких условиях он будет запускать постоянную rvalue-reference (и почему s6 не запускает ее?)

Существующие объекты будут проходить как string& или const string& в зависимости от того, являются ли они const или нет. Они также могут быть скопированы как string. Temporaries будут проходить как string&&, но также могут быть скопированы как string. Существуют способы запуска const string&&, но нет причин делать это когда-либо, поэтому это не имеет значения. Они показаны здесь.

Почему непостоянная ссылка и постоянная ссылка rvalue являются незаконными?

В стандарте конкретно говорится, что только const string& и string&& будут продлевать жизнь временным образом, хотя я не уверен, почему они не упомянули также string& и const string&&.

Я ожидал, что нельзя изменить s3, но почему b во внутренней области может изменить s3? Если присваивание нового объекта s3 на b назначает новую ссылку, почему, когда я назначаю s4 ему, а s3 изменился и s4 пуст после?

Вы инициализировали b как ссылку на s3. Не копия, а ссылка. Это означает, что b теперь ссылается на s3 навсегда, независимо от того, что. когда вы набрали b = "b changed after assigning s3\n";, точно так же, как s3 = "b changed after assigning s3\n";. Когда вы набрали b = s4;, это точно так же, как s3 = s4. Вот что такое ссылка. Их нельзя "перепроверять".

Ответ 2

rvalues ​​могут связываться с ссылками rvalue и ссылками на константу l, например,

void foo(const string&);
void bar(string&&);

foo(string{});
bar(string{});

Но rvalue не может связываться с неконстантными ссылками lvalue. Разрешение перегрузки предпочитает привязывать временные значения к rvalue-refs по привязке их к константам lvalue refs:

void foo(const string&);
void foo(string&&);

foo(string{});           // will call the second overload

lvalues ​​может привязываться только к значениям lvalue. Обратите внимание, однако, что const ограничивает это:

const string do_not_modify_me;
string& modify_me = do_not_modify_me;  // not allowed, because `do_not_modify_me`
modify_me += "modified";               // shall not be modified: declared as `const`

Вы можете std::move lvalues ​​связывать их с rvalue ссылками:

string s;
string&& r = std::move(s);

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


paramCheck("");         // a string literal is an lvalue (!)
                        // see [expr.prim.general]/1
                        // but it is implicitly converted to a `std::string`,
                        // creating a `string` temporary, a rvalue

paramCheck(string{""}); // a temporary is an rvalue

string s3{""};
paramCheck(s3);         // the variable `s3` is an lvalue of type `string`

const string s4{""};
paramCheck(s4);         // the variable `s4` is an lvalue of type `const string`

//Illegal
//string& s{""};        // can't bind a temporary to a non-const lvalue ref
//paramCheck(s);

const string& s5{s3};
paramCheck(s5);         // the variable `s5` is a lvalue of type `const string`

string&& s6{""};        // binding a temporary to a rvalue-ref (allowed)
paramCheck(s6);         // the variable `s6` is an lvalue (!) - it has a name

//Illegal
//const string&& s{s1}; // `s1` has not been declared
//onstFP(s);

//Reference test
string a = s3;          // copy the contents of `s3` to a new string `a`
a = "a changed s3";     // overwrite contents of `a`
cout << s3;

{
string& b = s3;         // `b` refers to `s3` now (like an alias)
b = "b changed after assigning s3\n";
cout << "s3 is now " <<s3;

b = s4;                 // copy the contents of `s4` to `b` (i.e. to `s3`)
b = "b changed after assigning s4\n";
cout << "s3 is now " <<s3;
cout << "s4 is now " <<s4;
}

Если мы передаем константное выражение, оно всегда вызывает непостоянный rvalue-reference? При каких условиях он будет запускать постоянную rvalue-reference (и почему s6 не запускает ее?)

Константное выражение может содержать только (значения lvalue-to-rvalue) объектов, объявленных либо constexpr, либо const, либо временные, которые являются значениями r. Следовательно, AFAIK, постоянное выражение не может давать неконстантное lvalue.


Почему непостоянная ссылка и постоянная ссылка rvalue являются незаконными?

Оба разрешены, фактически. Хотя const rvalue refs не имеет для меня никакого смысла, вы можете использовать const lvalue-refs.


Я ожидал, что нельзя изменить s3, но почему b во внутренней области может изменить s3? Если присваивание нового объекта s3 на b назначает новую ссылку, почему, когда я назначаю s4 ему, а s3 изменился и s4 пуст после?

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

Ответ 3

Просто чтобы ответить на эту часть:

При каких условиях он будет запускать постоянную ссылку rvalue-reference

Постоянная rvalue-reference overload будет использоваться, когда вы вызываете ее с rvalue константного типа:

void paramCheck (const string&& s) {
    cout << ++i  << ". Param is const rvalue-reference.\n";
}

const std::string functionThatReturnsConstantRvalue() { return ""; }

// ...

paramCheck( functionThatReturnsConstantRvalue() );

const std::string s;
paramCheck( std::move(s) );

В общих функциях, принимающих a const X&&, бесполезны, потому что вы не можете перейти от константы. Они могут быть полезны в качестве удаленных функций для предотвращения компиляции определенных вызовов.

Ответ 4

Если мы передаем константное выражение, оно всегда вызывает непостоянный rvalue-reference? При каких условиях он будет запускать постоянную rvalue-reference (и почему s6 не запускает ее?)

Для постоянного выражения? Никто. Единственный раз, когда что-то будет привязано к const&&, будет, если оно уже const. И даже тогда для этого потребуется явный приведение, если это переменная (см. Ниже).

Почему непостоянная ссылка и постоянная ссылка rvalue являются незаконными?

Я предполагаю, что вы говорите об этом:

//string& s{""};
//paramCheck(s);

//const string&& s{s1};
//onstFP(s);

Первое является незаконным, поскольку "" не является переменной std::string. Следовательно, он должен построить std::string временный из "". s - это неконстантная ссылка на существующую строковую переменную. Вы не можете использовать неконстантную ссылку на временную, так как временная не является переменной.

Второе незаконно, потому что (игнорируя тот факт, что s1 не существует), С++ не позволяет вам получить ссылку на переменную r-значение без явного преобразования. Для этого используется std::move. const string &&s{std::move(s3)} отлично работает.

Я ожидал, что нельзя изменить s3, но почему b во внутренней области может изменить s3? Если присваивание нового объекта s3 на b назначает новую ссылку, почему, когда я назначаю s4 ему, а s3 изменился и s4 пуст после?

Во-первых, вы можете просто изменить s3. b является ссылкой на s3; это два имени для одного и того же объекта. Что касается остальных, вы не можете изменить, на какой объект ссылается b после создания b. b начинает ссылаться на s3, поэтому он всегда будет делать это. Таким образом, b = s4 означает копирование s4 в любой объект, на который ссылается b, который является s3.

s4 пуст, потому что он всегда пуст. Вы назначили ему пустую строку. Поэтому он пуст.

Ответ 5

Вы должны перестать думать о Foo&& как ссылку rvalue. Подумайте скорее о том, к чему привязаны.

Функция, принимающая Foo&&, привязывается только к временному Foo s или Foo, помеченному как временное.

Эта временная маркировка не длится. Если у вас есть переменная Foo&& foo, и вы ее используете, она не будет помечена как временная в точке использования. Маркировка чего-то временного может произойти немедленно - функцией, возвращающей Foo&&, или путем возврата анонимного Foo, который при его немедленном использовании считается временным.

Стандартными способами пометки данных как временных является (A), это анонимный экземпляр Foo, который является временным, (B) вы вызвали std::move в экземпляре Foo, (C), который вы вызывали std::forward<Foo> в экземпляре Foo.

На практике && используется как по так называемым универсальным ссылкам, так и по ссылкам, которые вы хотите привязать к временным. В контексте вывода типа ссылки lvalue могут быть сохранены в T&&, делая T в Foo& - ссылка lvalue "выигрывает" над ссылкой rvalue. Это та ситуация, когда вам нужно позвонить std::forward, чтобы условно переместить.

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

  • Когда вы принимаете аргумент, который вы хотите переместить, из списка аргументов функции или метода.
  • Когда вы используете совершенную пересылку и универсальную справочную технику в аргументах функции template.
  • Когда вы выполняете передачу совершенного перенаправленного параметра в возвращаемое значение.
  • Когда вы делаете универсальную справочную технику для создания ссылки на возможно-временную область действия (например, for(auto&& i:x)).

При использовании названной переменной && она действует почти так же, как переменная & или const &. Чтобы использовать его так, чтобы он считался временным, вам нужно std::move или в универсальном справочном контексте использовать std::forward для условного std::move.