본문 바로가기

[C++] decltype 이란?

Kwonriver 2022. 1. 22.
728x90

decltype은 주어진 이름이나 표현식의 형식을 알려주는 역할을 한다.

( decltype에 대한 정보 https://msdn.microsoft.com/ko-kr/library/dd537655.aspx )

 
 
decltype은 대부분 예상한 자료형을 알려주지만 가끔 생각지도 못한 결과를 제공하는 경우가 있다.
그렇기에 decltype의 작동 방식을 공부해보자.
 
decltype은 템플릿이나 auto와 다르게 주어진 이름이나 표현식의 구체적인 자료형을 그대로 말해준다.
다음 예를 보자.
 
위 예에서 사용된 코드들은 우리가 생각할 수 있는 그대로가 decltype을 통해 표현된다.
 
대체로 자료형 T의 객체들을 담은 컨테이너(vector, deque 등)에 대한 operator[] 연산은 T&를 반환한다.
그러나 std::vector<bool>에 대한 operator[]연산은 bool&가 아닌 새로운 객체를 반환한다.
이처럼 operator[]의 반환 형식이 컨테이너에 따라 다를 수 있다.
decltype을 이용하면 그런 함수의 반환 형식을 손쉽게 표현할 수 있다.
 
다음 함수를 보자. ( 다음 함수는 정련되지 않은 함수 )
함수 이름 앞에 auto를 사용하는 것은 자료형 추론과는 연관이 없다.
여기서 사용된 auto는 C++의 후행 반환 형식 구문이 쓰인다는 점을 나타낸다
후행 반환 형식 : 반환 형식을 이 위치가 아니라 매개변수 목록 다음에 선언하겠다는 의미 )
즉, decltype(c[i])의 자료형이 반환형이 된다.
만일 통상적인 방식인 함수 이름 앞(현재 auto의 위치)에 반환형을 지정하면 c와 i는 사용할 수 없다.
c와 i의 자료형이 명시되지 않았기 때문이다.
 
C++11은 람다 함수가 한 문장으로 이루어져 있는 경우 그 반환형의 추론을 허용하며 C++14는 허용 범위를 더 확장하여 모든 람다와 모든 함수의 반환형의 추론을 허용한다.
따라서 위의 함수(autoAndAccess)의 경우 C++11에서는 위와같이 사용해야 하지만 C++14에서는 후행 반환 형식을 생략하고 auto만 사용해도 에러가 발생하지 않는다( C++14는 visual studio 2015부터 지원 )
내 visual studio 버전은 2013이어서 후행 반환 형식이 필요하다.....
 
T 객체를 담은 컨테이너의 operator[] 연산들은 대부분 T&를 반환한다.
그러나 템플릿 자료형 추론 과정에서 참조(&)는 생략된다.
 
이런 식의 사용의 경우 authAndAccess 함수가 v[3]을 반환하게 되고 그 반환값에 10을 넣겠다는 의미가 된다.
그런데 문제는 반환되는 과정에서 int&형인 v[3]이 추론되는 것이 아닌 int형인 v[3]이 추론된다는 것이다.(참조가 무시되면서)
함수의 반환값으로서의 이 int는 rvalue(오른값)이다.
C++에서 rvalue에 값을 대입할 수 없으므로 이 코드는 에러가 나야한다.
 
이를 원하는 대로 작동(v[3]에 10을 대입)하기 위해서 C++14에서는 decltype(auto)를 만들었다.
C++14 버전으로 위 함수를 변경해보면 다음과 같다.
(물론 내 비쥬얼스튜디오에서는 컴파일 에러남;;;;)
이렇게 되면 autuAndAccess 함수의 반환형은 c[i]의 반환형과 완전히 일치하게 된다.
c[i]가 T&를 반환하는 일반적인 경우 T&를 반환하고, c[i]가 하나의 객체를 돌려주는 경우 마찬가지로 같은 형식의 객체를 반환한다.
 
decltype(auto)는 함수 반환형 뿐만 아니라 변수 선언에서도 사용할 수 있다.
에러는 무시하자. C++14를 지원하지 않아서 그런것이다.
 
위에서 autuAndAccess 함수가 정련되지 않았다고 하였다.
그럼 이제 정련해보자.
현재 authAndAccess 함수의 컨테이너 c는 비const객체에 대한 왼값 참조로써 함수에 전달되고있다.
이는 함수가 반환한 컨테이너 요소를 사용자가 수정할 수 있게 하기 위해서이다.
그런데 이러한 설정때문에 오른값 컨테이너를 매개변수로 넣을 수 없게 되었다.
오른값 컨테이너를 왼값 참조에 넣는 것은 극단적인 경우지만 그런 경우가 있을 수 있으니 대비하자.
함수 오버로딩을 사용하여 두 개의 함수를 만들 수도 있으나 같은 일을 하는 함수 두 개를 만들면 관리할 것도 두 개가 된다.
따라서 왼값과 오른값 모두에 묶일 수 있는 참조 매개변수를 사용하는 방법을 사용하자.
이럴 때 사용하는 것이 보편참조(&&)이다.
 
보편 참조에 std::forward를 적용하기도 한다.
그런데 위 함수는 C++11에서 사용할 수 없다
그래서 함수의 반환값을 더 명시적으로 지정하기 위해 후행 반환 형식을 사용하여 표현하자.
C++11에 맞는 함수이다.

위의 내용들은 보편적인 decltype의 작동방식이었다.

그럼 보편적이지 않은 작동 방식은 무엇이 있을까

decltype이 아주 가끔 예기치 못한 결과를 나타낸다.

 

decltype을 이름에 적용하면 그 이름에 대해 선언된 자료형이 산출된다 ( int x = 0 에서 x가 이름 )

대체로 이름은 왼값 표현식이나 decltype에 영향을 미치는 것은 아니다.

그러나 이름보다 더 복잡한 왼값 표현식에 대해서는 일반적으로 decltype은 항상 왼값 참조를 산출한다.

 

int x = 0 이 있다고 할 때 decltype(x)는 int 이다.

그런데 이 x를 괄호로 감싼 (x)는 이름보다 복잡한 표현식이다.

C++에서는 (x)라는 표현식도 왼값으로 정의한다.

따라서 decltype((x))는 int&이 된다.

decltype(auto)가 없는 C++11 수준에서는 그냥 하나의 예외 정도이지만 C++14에서는 이야기가 다르다.

return 문 작성의 차이로 인해 반환형의 추론 결과가 달라질 수도 있기 때문이다.

 

함수 두 개가 있다

(auto 밑의 에러줄은 무시하자. C++14를 지원하지 않아서 그런것이다)

 

그냥 보면 차이점이 없다.

실제로 func1 처럼 사용하는 사람도 있고 func2 처럼 사용하는 사람도 있다.

난 func1 처럼 사용한다 ( return 뒤에 괄호 사용 안함 )

만일 함수의 반환형을 int로 명시적으로 선언했다면 반환값은 모두 int 형이 될 것이다.

그런데 decltype(auto)를 사용함으로써 완전히 다른 반환값이 된다.

func1의 반환값은 decltype(x) 이므로 int 형이고 func2의 반환값은 decltype((x))이므로 int&가 될 것이다.

 

이러한 차이점이 있으므로 decltype(auto)를 사용할 때에는 조심해야한다.

 

물론 비쥬얼스튜디오2013이하 버전에서는 사용할 일이 없지만 나중에 어떻게 될지 모르는 것이니 알고 있어야한다.

728x90