본문으로 바로가기

C++11) decltype (형식 지정자)

category C++/Modern 2019. 2. 3. 22:41

<개요>


이번엔 c+11 에서 생긴 decltype에 대해 소개합니다.

decltype전에 auto를 모르시는분은 auto를 보고 오시는게 좋을 것 같습니다.


<decltype>


decltype 은 C++11 에서 추가된 키워드로서 auto가 값에 맞는 타입을 추론해준다면, decltype은 값에 맞는 타입을 추출해낸다고 보시면 될 것 같습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
class c{
public:
    float a;
};
int main() {
    int x = 0;
    const int&& fx();
 
    decltype(x);        //int
    decltype((x));        //int&
    decltype(fx());        //const int&&
 
    c cc;
    decltype(cc.a);        //float
 
    const c ccc;
    decltype((ccc.a));// const float&
}
cs



1. decltype(x) 는 x가 지역변수 이므로 int 형입니다.
2. decltype((x)) 는 x가 지역변수이고, 내부 괄호를 사용하여 참조로 판단합니다. int& 형입니다.
3. decltype(fx())는 fx()가 const int&& 형이기때문에 const int&& 형으로 판단합니다.
4. decltype(cc.a)는 c클래스 내의 멤버 변수 a 이기때문에 float 형입니다.
5. decltype((ccc.a)) 는 ccc.a 가 float 형이나, 내부 괄호로 감싸졌기때문에 이는 멤버 엑세스가 아닌 식으로 평가됩니다. 그러므로 const float& 형입니다.
추가점) decltype(1)int&& 형입니다, decltype(ccc.a) 라면 이는 const 형이지만 멤버 엑세스이므로 float 형입니다.

사실 변수에서는 decltype 보단 auto 사용을 권고하고 있습니다.
하지만 템플릿같이 컴파일 시간내에 타입을 추론해야하는 경우엔, decltype을 사용해야 합니다.


예를 들어 자기가 글자를 더해야 하거나, 값을 더해야 하거나 하는 경우를 위해 plus 라는 템플릿 변수를 하나 선언합니다.
1
2
3
4
5
 
template<typename T, typename U>
? ? ?& plus(T&& lhs, T&& rhs) {
    return (std::forward<T>(lhs) + std::forward<U>(rhs));
};
cs

이 경우엔 대체 어떤 반환타입이 들어가야할까요?
C++11 과 C++14 이후에 대해 설명합니다.

<C++11 decltype>

C++11에선 auto 타입 반환이 가능해지고, 후행 반환 형식 (trailing return type) 으로 decltype 을 사용하고 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
 
template<typename T, typename U> 
auto Plus(T&& lhs, U&& rhs) -> decltype(std::forward<T>(lhs) + std::forward<U>(rhs)) {     
    return (std::forward<T>(lhs) + std::forward<U>(rhs));
};
 
int main() {
    string A("abc");
    string B("def");
    cout << Plus(A, B) << endl;     // abcdef
    cout << Plus(1020<< endl;    // 30
}
cs

C++11 에선 single statement lambda (위의 decltype 처럼 단일의 람다) 의 리턴 타입만 추론이 가능하게 됩니다.
auto 반환 함수는 반드시 후행 반환 형식을 지정해 주어야 하며, 컴파일 단계에서 인자들을 가지고 타입을 추론해야 하므로
auto만 사용할 시 오류가 발생합니다.
또한 위의 템플릿 함수는 두 값을 묶어 새로운 생성자로 전달 하고 있습니다. 위와 같은 상황에선 후행 반환 형식이 꼭
리턴 형식과 동일 해야 합니다.
그렇지 않으면 rvalue가 들어왔을때 lvalue로 타입을 추론할 수도 있기 때문입니다.

<C++14 decltype>

C++14 에선 모든 람다와 모든 함수 에서 리턴 타입을 추론할 수 있게됐으며 decltype 의 후행 반환 형식이 삭제되었습니다. (삭제는 아니고... 안써도 됨..)
1
2
3
4
5
6
7
8
9
10
11
12
 
template<typename T, typename U> 
auto Plus(T&& lhs, U&& rhs){     
    return (std::forward<T>(lhs) + std::forward<U>(rhs));
};
 
int main() {
    string A("abc");
    string B("def");
    cout << Plus(A, B) << endl;     //abcdef
    cout << Plus(1020<< endl;     //30
}
cs

위는 auto가 자동으로 리턴 타입을 결정해줍니다. 하지만 좋지 않은 방식입니다.
왜냐면 auto는 다른 키워드나 타입이 붙지 않을 시 복사본이 된다는 점입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;
class c {
    int arr[10= { 1,2,3,4,5,6,7,8,9,10 };
public:
    
    int& operator[](int i) { return arr[i]; }
};
 
template<typename T>
auto changearr(T& c, int index) {
    return c[index];
};
 
int main() {
    c myclass;
    changearr(myclass, 3= 100;    // 이런, 복사값이기 때문에 변하지 않아요.
    cout << myclass[3];
}
cs


c 클래스에서는 10개의 배열을 초기화해 []오버로딩으로 값을 읽고 쓰게 해줬습니다. 
그러나 c[index]의 반환형은 int& 인것에 비해, auto 키워드 자체만으론 복사본을 리턴하기 때문에 저 3번째 배열은 영영 변하지 않을것입니다.
하지만 decltype을 사용해 준다면 변경 가능합니다.

1
2
3
4
5
6
7
8
9
10
11
 
template<typename T>
decltype(auto) changearr(T& c, int index) {
    return c[index];
};
 
int main() {
    c myclass;
    changearr(myclass, 3= 100;
    cout << myclass[3];
}
cs

이렇게 리턴타입을 decltype(auto)로하면, auto의 복사본을 반환형으로 하는게 아닌, c[index]를 가지고 반환 추론을 하기 때문에 int& 형으로 반환됩니다.
또한 T가 rvalue (임시객체) 로 들어올 가능성도 있기때문에 꼭 universal reference로 받아주셔야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
class c {
    int arr[10= { 1,2,3,4,5,6,7,8,9,10 };
public:
    
    int& operator[](int i) { return arr[i]; }
};
 
template<typename T>
decltype(auto) changearr(T&& c, int index) {
    return std::forward<T>(c)[index];
};
 
int main() {
    auto i = changearr(c(), 3);    // c는 rvalue 입니다. i는 rvalue를 auto로 가져왔기에 int& 형입니다.
    cout << i << endl;    // 이러면 c의 3번째 배열인 4가 출력되겠죠?
}
cs


아악 decltype은 다 이해 했는데 아직도 rvalue lvalue가 절 괴롭히고 있습니다.
도움받은 링크 : https://docs.microsoft.com/ko-kr/cpp/cpp/decltype-cpp?view=vs-2017