[C++] decltype 이란?
decltype은 주어진 이름이나 표현식의 형식을 알려주는 역할을 한다. decltype은 대부분 예상한 자료형을 알려주지만 가끔 생각지도 못한 결과를 제공하는 경우가 있다. 그렇기에 decltype의 작동 방식을 알고 있는 것이 좋다. decltype은 템플릿이나 auto와 다르게 주어진 이름이나 표현식의 구체적인 자료형을 그대로 말해준다.
위 예제에서 사용된 코드들은 우리가 생각할 수 있는 그대로가 decltype을 통해 표현된다.
대체로 자료형 T의 객체들을 담은 컨테이너(vector, deque 등)에 대한 operator[] 연산은 T&을 반환한다. 그러나 std::vector<bool>에 대한 operator[] 연산은 bool&가 아닌 새로운 객체를 반환한다. 이처럼 operator[]의 반환 형식은 컨테이너에 따라 다를 수 있다. 이럴 때 decltype을 이용하면 함수의 반환 형식을 쉽게 표현할 수 있다.
아래 함수를 살펴보자. 단, 이 함수는 정련되지 않았다.
함수 이름 앞에 auto를 사용하는 것은 자료형 추론과는 연관이 없다. 여기서 사용된 auto는 C++의 후행 반환 형식 구문이 쓰인다는 점을 나타내고 있을 뿐이다. (후행 반환 형식 : 반환 형식을 이 위치가 아닌 매개변수 목록 다음에 선언하겠다는 의미 )
즉, decltype(c[i])의 자료형이 반환형이 된다. 만일 우리가 흔히 사용하는 통상적인 방식의 함수 정의 방식으로 함수의 반환형을 함수 앞에 명시한다면 c와 i를 사용하여 반환할 수 없게 된다. 함수만 보았을 때에는 template로 인해 c, i의 자료형을 알 수 없기 때문이다.
C++11은 람다 함수가 한 문장으로 이루어져 있는 경우 그 반환형의 추론을 허용하며 C++14는 허용 범위를 더욱 확장하여 모든 람다와 모든 함수의 반환형의 추론을 허용한다. 따라서 위 예제 함수(authAndAccess)의 경우 C++11에서는 위 코드처럼 decltype을 명시하여야 하지만 C++14에서는 후행 반환 형식을 생략하고 반환형에 auto만 사용하여도 에러가 발생하지 않는다. C++14는 Visual Studio 2015부터 지원한다.
T객체를 담은 컨테이너의 operator[] 연산들은 대부분 T&를 반환한다. 그러나 템플릿 자료형 추론 과정에서 참조(&)는 생략된다.
위 예제 코드처럼 사용하는 경우 authAndAccess 함수가 v[3]를 반환하게 되고 그 반환값에 10을 넣겠다는 의미가 된다. 그런데 문제는 반환하는 과정에서 참조가 무시되면서 실제 자료형인 int&로 v[3]가 추론되는 것이 아닌 int 자료형으로 추론된다는 것이다. 즉, 함수의 반환값이 rvalue가 되어 값을 대입할 수 없다.
C++14에서는 반환값에 값을 대입하는 동작을 정상적으로 작동하도록 하기 위해 decltype(auto)를 만들었다. C++14 버전으로 authAndAccess 함수를 다시 만든다면 아래 코드와 같다.
이렇게 하면 authAndAccess 함수의 반환형은 c[i]의 반환형과 완벽하게 일치하게 된다. c[i]가 T&를 반환하는 일반적인 경우 T&를 반환하고, c[i]가 하나의 객체를 돌려주는 경우에도 마찬가지로 같은 형식의 객체를 반환한다.
decltype(auto)는 함수 반환형 뿐 아니라 변수 선언에도 사용할 수 있다.

위에서 authAndAccess 함수가 정련되지 않았다고 하였다. 그럼 이제 정련해보도록 하자.
현재 authAndAccess 함수의 컨테이너 c는 '비 const' 객체에 대한 왼쪽값 참조로써 함수에 전달되고 있다. 이는 함수가 반환한 컨테이너 요소를 사용자가 수정할 수 있게 하기 위해서이다. 그런데 이러한 설정 때문에 오른쪽 값 컨테이너를 매개변수로 넣을 수 없게 되었다.
오른쪽 값 컨테이너를 왼쪽값 참조에 넣는 것은 잘 발생하지 않는 극단적인 경우이지만 그런 경우가 있을 수 있으니 대비하는 것이 좋다. 함수 오버로딩을 사용하여 두 개의 함수를 만들어서 사용할 수도 있으나 같은 일을 하는 함수 2개를 만드는 것 자체가 유지보수에 좋지 않다. 따라서 왼값과 오른값 모두에 묶일 수 있는 참조 매개변수를 사용하는 방법을 사용하는 것이 좋다. 이럴 때 보편참조(&&)를 사용한다.


앞서 설명한 내용들은 모두 보편적인 decltype의 작동 방식이었다. 그렇다면 보편적이지 않은 방식은 무엇이 있을까. decltype은 아주 가끔 예기치 못한 결과를 나타낸다.
decltype을 이름에 적용하면 그 이름에 대해 선언된 자료형이 산출된다. (변수명) 대체로 이름은 왼값 표현식이나 decltype에 영향을 미치는 것은 아니지만 이름보다 더 복잡한 왼값 표현식에 대해서는 일반적으로 decltype은 항상 왼쪽값 참조를 산출한다. int x = 0 이 있다고 할 때 decltype(x)는 int 로 산출된다. 그런데 이 x를 괄호로 감싼 (x)는 이름보다 더 복잡한 표현식이다. C++에서는 (x)라는 표현식도 왼값으로 정의하기 때문이다. 따라서 decltype((x))는 int&가 된다.
decltype(auto)가 없는 C++11 수준에서는 그냥 하나의 예외 정도이지만 C++14에서는 이야기가 다르다. return 문 작성의 차이로 인해 반환형의 추론 결과가 달라질 수도 있기 때문이다.
위처럼 함수 2개가 있다. (빨간색 에러 줄은 무시하자. C++14를 지원하지 않는 컴파일러라 그렇다.) 우리가 흔히 생각하기로는 두 함수의 차이점이 없다. 실제로 func1 처럼 정의하는 사람도, func2 처럼 정의하는 사람도 있다. 필자는 대부분 func1 처럼 사용하는데 두 함수의 반환형이 int 같은 명시적 자료형이라면 두 함수 모두 int를 반환할 것이다.
그런데 decltype(auto)를 사용함으로써 두 함수의 반환값은 완전히 달라진다. func1의 반환값은 decltype(x) 이므로 int 형이고 func2의 반환값은 decltype((x))이므로 int&가 될 것이다. 이러한 큰 차이점이 존재하기 때문에 사용시 유의하여야 한다.
'프로그래밍' 카테고리의 다른 글
언리얼 엔진 4, 비쥬얼 스튜디오 2022 다운로드 및 설치 후 프로젝트 생성하기 (0) | 2023.02.09 |
---|---|
[STL] 컨테이너의 분류와 종류(순차 컨테이너, 연관 컨테이너, 비정렬 컨테이너) (0) | 2023.02.08 |
[C++] 레퍼런스(참조, &) 사용 시 주의 사항 (0) | 2023.02.08 |
[C++] 순수 가상 함수(pure virtual method)와 추상 클래스(abstract class) 연관 (0) | 2023.02.08 |
[C++] 함수 오버라이딩의 특수 케이스 확인하기 (0) | 2023.02.08 |