UNSEEN 2기 언리얼 컨퍼런스 스터디에서 C++ 문법 아래 내용 설명함.
int main()
{
f() = 1;
cout << f() << endl;
int j = 2;
cout << (f(j) = 3) << endl;
return 0;
}
#include <iostream>
using namespace std;
int a;
int& f() {
return a;
}
int& f(int _a) {
a = _a;
cout << a << endl;
return a;
}https://en.cppreference.com/w/cpp/language/value_category
void foo();
void baz()
{
int a; // Expression `a` is lvalue
a = 4; // OK, could appear on the left-hand side of an assignment expression
int &b{a}; // Expression `b` is lvalue
b = 5; // OK, could appear on the left-hand side of an assignment expression
const int &c{a}; // Expression `c` is lvalue
c = 6; // ill-formed, assignment of read-only reference
}identity를 가지면서 move될 수 없는 표현식들identity를 가지면서 move될 수 있는 표현식들identity를 가지고 있지 않으면서 move될 수 있는 표현식들identity를 가지고 있는 표현식들 (lvalue, xvlaue모두 glvalue 표현식)move될 수 있는 표현식들 (prvalue, xvalue 모두 rvalue 표현식)identity를 가지고 있지 않으면서 move될 수 없는 것들
move 될 수 있다’의 기준: 값이 메모리에서 이동될 수 있다. 
identity 가진다’의 의미 :#include <iostream>
static int a = 10; // [참고 링크 2번](https://mr-dingo.github.io/c/c++%EB%BD%80%EA%B0%9C%EA%B8%B0/2019/01/10/static&extern.html) - 전역 변수의 static 의미와 extern
int& foo() {
a++;
return a;
}
int foo2(){
return a;
}
// 좌측값 (Lvalue)
int main(){
int a = 10, b = 10, c = 10;
int *j = &a; // 메모리 주소를 참조 가능하기 때문에 a는 좌측값(Lvalue)
std::cout << "*j = " << *j << "\n"; // 10
foo() = 43; // foo()는 좌측값(Lvalue)
std::cout << "foo() = " << foo() << "\n"; // 44
int *ptr1 = &foo(); // &foo()가능하기 때문에 좌측값(Lvalue)
std::cout << "*ptr1 = " << *ptr1 << ""; // 45
++a; // a는 lvalue, pre-increasement, pre-decreasement
std::cout << "a = " << a << "\n"; // 11
int cc[4];
std::cout << "cc[1] = " << cc[1] << "\n"; // c[1]도 lvalue // 0
a ? b : c; // a ? b : c 의 반환값은 b 또는 c이므로 lvalue
}
특징
int a = 0; // a는 lvalue, 0은 prvalue
int&& c = std::move(a); // std::move(a)는 xvalue, c는 lvalue

identity를 가지면서 move될 수 있는 표현식들을 xvalue라고 한다. eXpiring에서 따와 xvalue라고 부른다. xvalue는 말 그대로 만료되어가는 값을 뜻한다. 그래서 표현식이 끝나고 표현식이 의미하던 주소로 접근했을 때 값이 존재할 수도 있고, 존재하지 않을 수도 있다. 위의 그림 처럼 이동될 수 있지만, 메모리에 올라가는 순간 주소를 갖게 되므로 prvalue는 될 수 없다. 컴파일러는 이러한 prvalue의 임시데이터를 저장할 공간이 필요한데, 이러한 임시 데이터 객체를 xvalue라고 한다. 예를 들어 std::move(x)와 같이 rvalue reference를 리턴할 수 있는 함수는 lvalue를 move하고, move한 값은 xvalue에 속하게 된다.
특징
int a = 10;
int b = 20;
a++;
a+b;
if(a < b) { // a < b의 결과값 bool은 prvalue
}
prvalue는 pure rvalue의 약자로 후위 증감연산자, 문자열 리터럴을 제외한 모든 리터럴 등이 prvalue에 속한다. prvalue는 대입문 오른쪽에 올 수 있으며 주소가 없다는 특징을 가지고 있다.
특징
const type & 은 좌측값과 우측값을 모두 받을 수 있다.const type * 초기화할 때 좌측값과 우측값을 모두 받을 수 있는 것과 마찬가지이다.#include <stdlib.h>
#include <iostream>
#include <vector>
using namespace std;
class MyString
{
int id;
int size;
int length;
char* data;
public:
static int cnt;
MyString() : size(0), length(0), data(nullptr), id(++cnt) { cout << "빈 문자열 생성\n"; }
MyString(const char* str)
{
id = ++cnt;
cout << id << " 문자열 생성\n";
size = length = strlen(str);
data = new char[length];
for (int i = 0; i < length; ++i)
{
data[i] = str[i];
}
}
MyString(const MyString& str)
{
id = ++cnt;
cout << id << " 복사 생성\n";
size = str.size;
length = str.length;
if (size < length)
{
size = length;
}
data = new char[size];
for (int i = 0; i < length; ++i)
{
data[i] = str.data[i];
}
}
MyString(MyString&& ref) noexcept
{
id = ++cnt;
cout << id << " 이동 생성\n";
size = ref.size;
length = ref.length;
if (size < length)
{
size = length;
}
data = ref.data;
ref.length = 0;
ref.size = 0;
ref.data = nullptr;
}
~MyString()
{
cout << id << "해제\n";
--cnt;
if (data)
{
delete[] data;
}
}
MyString& operator=(const MyString& str)
{
cout << "복사\n";
if (size < str.length)
{
delete[] data;
size = str.length;
data = new char[size];
}
length = str.length;
for (int i = 0; i < length; ++i)
{
data[i] = str.data[i];
}
return (*this);
}
MyString& operator=(MyString&& str) noexcept
{
cout << "이동\n";
delete[] data;
size = str.size;
length = str.length;
if (size < length)
{
size = length;
}
data = str.data;
str.data = nullptr;
return (*this);
}
int Length() const
{
return length;
}
void Print() const
{
cout << "id: " << id << '\t';
for (int i = 0; i < length; ++i)
{
cout << data[i];
}
cout << "\n";
}
};
int MyString::cnt = 0;
template <typename T>
void MySwap(T& a, T& b)
{
T tmp(a);
a = b;
b = tmp;
}
int main(void)
{
MyString str1("abc");
MyString str2("def");
cout << "str1: ";
str1.Print();
cout << "str2: ";
str2.Print();
cout << "===SWAP===\n";
MySwap(str1, str2);
cout << "str1: ";
str1.Print();
cout << "str2: ";
str2.Print();
} 
#include <stdlib.h>
#include <iostream>
#include <vector>
using namespace std;
class MyString
{
int id;
int size;
int length;
char* data;
public:
static int cnt;
MyString() : size(0), length(0), data(nullptr), id(++cnt) { cout << "빈 문자열 생성\n"; }
MyString(const char* str)
{
id = ++cnt;
cout << id << " 문자열 생성\n";
size = length = strlen(str);
data = new char[length];
for (int i = 0; i < length; ++i)
{
data[i] = str[i];
}
}
MyString(const MyString& str)
{
id = ++cnt;
cout << id << " 복사 생성\n";
size = str.size;
length = str.length;
if (size < length)
{
size = length;
}
data = new char[size];
for (int i = 0; i < length; ++i)
{
data[i] = str.data[i];
}
}
MyString(MyString&& ref) noexcept
{
id = ++cnt;
cout << id << " 이동 생성\n";
size = ref.size;
length = ref.length;
if (size < length)
{
size = length;
}
data = ref.data;
ref.length = 0;
ref.size = 0;
ref.data = nullptr;
}
~MyString()
{
cout << id << "해제\n";
--cnt;
if (data)
{
delete[] data;
}
}
MyString& operator=(const MyString& str)
{
cout << "복사\n";
if (size < str.length)
{
delete[] data;
size = str.length;
data = new char[size];
}
length = str.length;
for (int i = 0; i < length; ++i)
{
data[i] = str.data[i];
}
return (*this);
}
MyString& operator=(MyString&& str) noexcept
{
cout << "이동\n";
delete[] data;
size = str.size;
length = str.length;
if (size < length)
{
size = length;
}
data = str.data;
str.data = nullptr;
return (*this);
}
int Length() const
{
return length;
}
void Print() const
{
cout << "id: " << id << '\t';
for (int i = 0; i < length; ++i)
{
cout << data[i];
}
cout << "\n";
}
};
int MyString::cnt = 0;
template <typename T>
void MySwap(T& a, T& b)
{
T tmp(move(a));
a = move(b);
b = move(tmp);
}
int main(void)
{
MyString str1("abc");
MyString str2("def");
cout << "str1: ";
str1.Print();
cout << "str2: ";
str2.Print();
cout << "===SWAP===\n";
MySwap(str1, str2);
cout << "str1: ";
str1.Print();
cout << "str2: ";
str2.Print();
} 
#include <iostream>
#include <vector>
template <typename T>
void wrapper(T& u) {
std::cout << "T& 로 추론됨" << std::endl;
g(u);
}
template <typename T>
void wrapper(const T& u) {
std::cout << "const T& 로 추론됨" << std::endl;
g(u);
}
class A {};
void g(A& a) { std::cout << "좌측값 레퍼런스 호출" << std::endl; }
void g(const A& a) { std::cout << "좌측값 상수 레퍼런스 호출" << std::endl; }
void g(A&& a) { std::cout << "우측값 레퍼런스 호출" << std::endl; }
int main() {
A a;
const A ca;
std::cout << "원본 --------" << std::endl;
g(a);
g(ca);
g(A());
std::cout << "Wrapper -----" << std::endl;
wrapper(a);
wrapper(ca);
wrapper(A());
}
std::forward를 사용했을 때#include <iostream>
template <typename T>
void wrapper(T&& u) {
g(std::forward<T>(u));
}
class A {};
void g(A& a) { std::cout << "좌측값 레퍼런스 호출" << std::endl; }
void g(const A& a) { std::cout << "좌측값 상수 레퍼런스 호출" << std::endl; }
void g(A&& a) { std::cout << "우측값 레퍼런스 호출" << std::endl; }
int main() {
A a;
const A ca;
std::cout << "원본 --------" << std::endl;
g(a);
g(ca);
g(A());
std::cout << "Wrapper -----" << std::endl;
wrapper(a);
wrapper(ca);
wrapper(A());
}

std::forward 레퍼런스 링크: https://en.cppreference.com/w/cpp/utility/forwardstd::forward<T>(u) 는 u가 우측값 레퍼런스 일 때에만 move를 적용해준다.template <class T>
T&& forward(typename std::remove_reference<T>::type& a) noexcept {
return static_cast<T&&>(a);
}A&&& forward(typename std::remove_reference<A&>::type& a) noexcept {
return static_cast<A&&&>(a);
}
A& forward(A& a) noexcept {
return static_cast<A&>(a);
}A&& forward(A& a) noexcept {
return static_cast<A>(a);
}std::remove_reference<T>::type 관련 레퍼런스 링크: https://en.cppreference.com/w/cpp/types/remove_referencetemplate<class T> struct remove_reference { typedef T type; };
template<class T> struct remove_reference<T&> { typedef T type; };
template<class T> struct remove_reference<T&&> { typedef T type; }; 그래서 매개변수가 T == A& 일때나 T == A 상관 없이 A&로 나온다.