일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- 암시적 변환
- 예외
- effective modern c++
- Effective c++
- 게임
- lua
- 언리얼
- 메타테이블
- 스마트 포인터
- 영화
- 영화 리뷰
- resource management class
- c++
- 오블완
- 다형성
- more effective c++
- 반복자
- 티스토리챌린지
- virtual function
- 함수 객체
- exception
- operator new
- UE4
- Smart Pointer
- 참조자
- reference
- 상속
- 비교 함수 객체
- implicit conversion
- effective stl
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 3. decltype 본문
728x90
항목 3. decltype의 작동 방식을 숙지하라
decltype
- 주어진 이름이나 표현식의 타입을 알려준다.
- 하지만 가끔 예상 밖의 결과를 제공하기도 한다.
- C++11에서는 함수의 리턴 타입을 매개변수 타입들로 추론해야 하는 함수 템플릿을 선언할 때 주로 사용한다.
decltype의 예측할 수 있는 결과
const int i = 0;
// decltype(i) = const int
bool f(const Widget& w);
// decltype(w) = const Widget&
// decltype(f) = bool(const Widget&)
struct Point
{
int x, y;
};
// decltype(Point::x) = int
// decltype(Point::y) = int
Widget w;
//decltype(w) = Widget
if(f(w)) ...
// decltype(f(w)) = bool
vector<int> v;
// decltype(v) = vector<int>
if(v[0] == 0) ...
// decltype(v[0]) = int&
리턴 타입 추론으로 사용되는 decltype
template<typename Container, typename Index>
auto autoAndAccess(Container& c, Index i) -> decltype(c[i]) // 사실 좀 더 정제 가능하다
{
authenticateUser();
return c[i];
}
- 함수 이름 앞의 auto는 타입 추론과는 아무런 관련이 없다.
- C++11의 후행 리턴 타입(trailing return type) 구문이라는 사실을 선언할 뿐이다.
- 후행 리턴 타입은, 리턴 타입을 매개변수들을 이용해서 지정할 수 있다는 장점이 있다.
- C++11은 람다 함수가 한 문장으로 이루어져 있다면 리턴 타입 추론을 허용한다.
- C++14는 허용 범위를 더욱 확장해서 모든 람다와 모든 함수의 리턴 타입 추론을 허용한다.
- 심지어 return 문이 여러 개인 함수조차도 허용한다.
- 그러므로 위 코드가 만약 C++14라면 그냥 함수 이름 앞의 auto만 남겨두어도 된다.
template<typename Container, typename Index>
auto autoAndAccess(Container& c, Index i) // C++14. 아주 정확하진 않다.
{
authenticateUser();
return c[i];
}
- 위 코드의 문제점
- T 객체를 담은 컨테이너의 operator[]는 대부분 T&를 돌려주지만..
- 템플릿 타입 추론 과정에서 초기화 표현식의 참조성(&)이 무시될 수 있다.(항목 1 참조)
std::deque<int> d;
...
authAndAccesss(d, 5) = 10; // 컴파일 에러!
// d[5]의 결과값인 int&를 리턴하는 걸 예상했지만,
// 템플릿에서 참조가 제거되기 때문에 리턴 타입은 int이다.
// rvalue에 rvalue를 넣는 건 금지되어 있기 때문에 컴파일 에러가 발생한다.
- 템플릿 타입 추론이 아니라, decltype 타입 추론 규칙이 적용되게 만들어야 한다.
- decltype(auto) 를 사용한다.
decltype(auto)
- auto는 해당 타입이 추론되어야 함을 뜻한다.
- decltype은 그 추론 과정에서 decltype 타입 추론 규칙이 적용되어야 함을 뜻한다.
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i) // C++14. 작동하지만 좀 더 정제 가능
{
authenticateUser();
return c[i];
}
- 함수 리턴 타입 뿐 아니라 변수 선언 시에도, 초기화 표현식에도 적용할 수 있다.
Widget w;
const Widget& cw = w;
auto myWidget1 = cw; // auto 타입 추론이 적용되어, 타입은 Widget
decltype(auto) myWidget2 = cw; // decltype 타입 추론이 적용되어, 타입은 const Widget&
authAndAccess 추가로 정제해보기
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container& c, Index i);
- 컨테이너 c는 비const 객체에 대한 lvalue로 함수에 전달된다.
- 허나, 이 때문에 함수에 오른값 컨테이너는 전달할 수 없다는 문제가 있다.
- rvalue 컨테이너를 넘기는 건 솔직히 극단적인 상황(edge case)이긴 하다.
- 하지만, 임시 객체를 넘겨줄 수 있게 만들어보는 건 여전히 가치가 있다.
- 아래처럼 그냥 임시 컨테이너의 한 요소의 복사본을 만들고 싶을수도 있기 때문이다.
std::deque<std::string> makeStringDeque();
// makeStringDeque가 돌려준 deque의 다섯 번째 요소의 복사본을 만든다.
auto s = authAndAccess(makeStringDeque(), 5);
- 함수를 오버로드 할 수도 있겠지만, 관리 이슈가 생긴다.
- lvalue, rvalue 모두에 바인딩 가능한 참조 매개변수를 authAndAccess에 도입하면 된다.
- 즉, 이 때 보편 참조를 사용하면 된다.(항목 24 참조)
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i); // 보편 참조를 사용한다.
- Index는 일단 값 전달로 남겨둔다.
- 물론 타입을 알 수 없기 때문에 복사로 인한 성능 하락이 심할 수도 있고...
- 객체 슬라이스 문제가 발생할 수도 있다.(항목 41 참조)
- 하지만 표준 라이브러리는 operator[]에서 인덱스 매개변수에 값 복사를 채용했다.
- 보편 참조에는 std::forward를 적용한다.(항목 25 참조)
template<typename Container, typename Index>
decltype(auto) authAndAccess(Container&& c, Index i) // C++14
{
authenticateUser();
return std::forward<Container>(c)[i]; // std::forward 적용
}
- C++11 컴파일러라면 아래처럼 한다.
template<typename Container, typename Index>
auto authAndAccess(Container&& c, Index i) -> decltype(std::forward<Container>(c)[i]) // C++11
{
authenticateUser();
return std::forward<Container>(c)[i]; // std::forward 적용
}
decltype의 예상 밖의 결과
- 거의 대부분 경험하기 힘들 것이다.
- 하지만 한 가지 정도는 알아두면 decltype 이해에 도움이 될 것 같다.
- decltype을 이름에 적용하면, 그 이름에 대해 선언된 타입이 산출된다.
- 그런데, 이름보다 복잡한 lvalue 표현식에 대해서는 일반적으로 decltype이 항상 lvalue 참조를 산출한다.
- 즉, 이름이 아니고 형식이 T인 어떤 lvalue 표현식에 대해 decltype은 T&를 산출한다.
- 어차피 lvalue 표현식은 보통 lvalue 참조를 포함하므로 이 때문에 문제가 생길 일은 거의 없다.
- 하지만, 아래처럼 미묘한 차이가 생긴다.
int x = 0;
decltype(x); // int이다.
// x는 변수의 이름이기 때문이다.
decltype((x)); // int&이다.
// (x)는 그냥 이름이 아니라 lvalue 표현식이기 때문이다.
- decltype(auto)를 지원하는 C++14에서는 return문 작성 습관의 미묘한 차이만으로도 리턴 타입 추론이 바뀌는 일이 생길 수 있다.
decltype(auto) f1()
{
int x = 0;
...
return x; // int 리턴
}
decltype(auto f2()
{
int x = 0;
...
return (x); // int& 리턴
}
- 두 번째 것은 지역 변수의 참조를 리턴하게 되므로 크리티컬해진다.
- 그러므로 decltype(auto)는 아주 조심해서 사용해야 한다.
728x90
'개발 > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 6. auto의 타입 추론 실패 (0) | 2025.02.12 |
---|---|
[Effective Modern C++] 5. 타입 명시보다 auto (0) | 2025.02.11 |
[Effective Modern C++] 4. 추론 타입 파악하기 (0) | 2025.02.10 |
[Effective Modern C++] 2. auto 타입 추론 규칙 (0) | 2025.02.06 |
[Effective Modern C++] 1. 템플릿 타입 추론 규칙 (0) | 2025.02.05 |
Comments