일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 | 29 | 30 | 31 |
- 인공지능
- 기능개발
- logistic regression
- Linear_regression
- AI
- leg
- Softmax classification
- pwnable.kr
- 텐서플로
- Python
- tensorflow
- Algorithm
- programmers
- Today
- Total
나혼자 공부장
[Modern Effective C++] Chap 1. Deducing Types (타입 추론) 본문
[Modern Effective C++] Chap 1. Deducing Types (타입 추론)
라부송 2020. 2. 10. 18:43Item 1. Understand template type deduction. (템플릿 타입 추론 규칙을 숙지하라)
템플릿 타입 추론 도중에 참조 타입의 인수들은 단 하나의 경우를 제외하고 참조성이 무시된다.
그 예외는 밑에서 차차 설명하도록 하겠다.
template<typename T>
void f(ParamType param); // ParamType : const T&
f(expr);
위와 같이 선언되어 있다면, expr에 int 형의 변수를 넣었을 때 최종 ParamType은 const int&가 된다.
즉 템플릿이 추론한 타입은 expr 뿐만이 아닌, ParamType의 형태에도 의존하게 된다.
그 경우는 총 세 가지로 나뉜다.
Case 1. ParamType이 포인터 혹은 참조타입이긴 하나, universal reference는 아님
여기서 universal reference (보편 참조) 란 무엇을 말하는 것인가?
간단하게만 설명하고 넘어가자면, const int&& 과 같이 && 형태는 주로 우측값 참조를 나타내는 건 알 것이다.
auto&& v2 = v
void f(T&& p)
위 두 경우와 같은 건 좌측값과 우측값 그 어느쪽이라고도 정의할 수 없으며 그 어느쪽도 될 수 있다. 두 종류를 모두 참조하는게 가능하다고 해서 universal 이라는 이름이 붙은 것이다.
1. expr이 참조 타입이라면, 참조 부분을 무시한다. (비참조 형식으로 만든다.)
2. expr을 ParamType에 패턴매칭 시켜서 최종적으로 T의 타입을 결정한다.
말로만 들었을 땐 직관적으로 와닿지 않으니 간단히 코드를 보자.
template <typename T>
void f(const T& param); // 이제는 param이 const에 대한 참조
int x = 27;
const int cx = x;
const int& rx = x;
f(x); // T는 int, param의 타입은 const int&
f(cx); // T는 int, param의 타입은 const int&
f(rx); // T는 int, param의 타입은 const int&
Case 2. ParamType이 universal reference임
1. 이 케이스에서 expr이 좌측값이라면, T와 ParamType은 모두 좌측값 참조로 추론된다. 이게 바로 위에서 설명한 T가 참조 타입으로 추론되는 단 하나의 예외다. ParamType 선언 구문은 마치 우측값 참조처럼 보이지만, 실제로는 좌측값 참조가 추론된다. 이게 바로 보편 참조의 특성이다.
2. expr이 우측값일 경우 정상적인 규칙들이 적용된다.
template<typename T>
void f(T&& param); // 이번에는 param이 보편 참조(universal reference)
int x = 27; // 이전과 동일
const int cx = x; // 이전과 동일
const int& rx = x; // 이전과 동일
f(x); // x는 좌측값, 따라서 T는 int&,
// param의 타입 역시 int&
f(cx); // cx는 좌측값, 따라서 T는 const int&,
// param의 타입 역시 const int&
f(rx); // rx는 좌측값, 따라서 T는 const int&,
// param의 타입 역시 const int&
f(27); // 27은 우측값, 따라서 T는 int,
// 그러므로 param의 타입은 int&&
Case 3. ParamType이 포인터도 아니고 참조도 아님
이 경우는 그저 함수에 값으로 전달되는 상황인 것이다. param은 완전히 독립적인 새로운 객체다.
이 사실 때문에 다음과 같은 규칙들이 적용된다.
1. expr 참조 부분 무시
2 const 무시
3. volatile 무시
template <typename T>
void f(T param); // 이번에는 param이 값으로 전달된다.
int x = 27; // 이전과 동일
const int cx = x; // 이전과 동일
const int& rx = x; // 이전과 동일
f(x); // T와 param의 타입은 둘 다 int
f(cx); // 여전히 T와 param의 타입은 둘 다 int
f(rx); // 이번에도 T와 param의 타입은 둘 다 int
template <typename T>
void (T param); // 인수는 param에 여전히 값으로 전달된다.
const char* const ptr = // ptr는 const 객체를 가리키는 const 포인터
"Fun with pointers";
f(ptr); // const char * const 타입의 인수를 전달
// 이 경우 포인터 자체(ptr)는 값으로
// 전달된다. 따라서 ptr의 상수성은
// 무시된다.
// 하지만, ptr이 가리키는 객체
// (여기서는 문자열)의 상수성은
// 여전히 보존된다.
// 그 결과 T와 param의 타입은
// 둘 다 const char*로
// 추론된다.
템플릿 타입 추론 과정에서 배열이나, 함수 이름에 해당하는 인수는 포인터로 붕괴한다.
int param[]은 int* param 과 사실상 같은 말이기에, 템플릿 함수에 값으로 전달되는 배열의 타입은 포인터 타입으로 추론된다. 즉 배열 타입의 매개변수라는 것은 없다.
그러나 참조를 이용해서 한가지 요령을 피워 진짜 배열에 가깝게 선언할 수는 있다.
template<typename T>
void f(T& param);
f(name);
// 배열을 f에 전달
이 경우 T는 const char [요소 수] 으로 추론되고 f의 매개변수는 const char (&) [요소 수] 로 추론된다.
함수 참조의 경우에도 마찬가지다.
void someFunc(int, double); // someFunc는 하나의 함수;
// 타입은 void(int, double)
template <typename T>
void f1(T param); // f1의 param은 값 전달 방식
template <typename T>
void f2(T& param); // f2의 param은 참조 전달 방식
f1(someFunc); // param은 함수 포인터로 추론됨;
// 타입은 void (*)(int, double)
f2(someFunc); // param은 함수 참조로 추론됨;
// 타입은 void (&)(int, double)
Item 2. Understand auto type deduction. (auto의 타입 추론 규칙을 숙지하라)
auto 타입 추론과 템플릿 타입 추론의 차이?
auto : 중괄호 초기치가 std::initializer_list 를 나타낸다고 가정
template : 그런 가정 없이 에러 발생 시킴
그 외에는 전반적으로 동일하다.
auto x = { 11, 23, 9 }; // x의 타입은
// std::initializer_list<int>
template <typename T> // x의 선언에 해당하는 매개변수
void f(T param); // 선언을 가진 템플릿
f({ 11, 23, 9 }); // 오류! T에 대한 타입을 추론할 수 없음
// 하지만 param의 타입이 어떤 알려지지 않은 T에 대한
// std::initializer_list<T>인 템플릿에 그 중괄호 초기치를
// 전달하면 템플릿 타입 추론 규칙들에 의해 T의 타입이
// 제대로 추론된다.
template <typename T>
void f(std::initializer_list<T> initList);
f({ 11, 23, 9 }); // T는 int로 추론되며, initList의 타입은
// std::initializer_list<int>로 추론된다.
그러나, 함수의 반환 타입이나 람다 매개변수에 쓰인 auto에 대해서는 템플릿 타입 추론의 규칙이 적용되므로 주의해야 한다.
auto createInitList(void)
{
return { 1, 2, 3 }; // 오류! { 1, 2, 3 }의 타입 추론 불가
}
Item 3. Understand decltype. (decltype의 작동 방식을 숙지하라)
decltype은 항상 변수나 표현식의 타입을 아무 수정 없이 보고한다.
decltype 이란?
auto가 값에 상응하는 자료형을 매칭시킨다면, decltype은 값으로부터 타입을 추출해 선언한다.
auto x = 3;
decltype(x) y = x; // auto y = x 와 같음
auto는 템플릿과 같이 참조성이 무시되기 때문에, int&가 int로 반환이 된다.
이 문제를 해결하기 위해 아래와 같이 정의할 수 있다.
template <typename Container, typename Index>
decltype(auto)
authAndAccess(Container& c, Index i)
{
authenticateUser();
return c[i];
}
decltype은 디폴트로 이름에 대해 선언된 타입이 산출되고, 그게 좌측값 표현식이라 하더라도 그게 decltype의 행동에 영향을 주진 않는다.
그러나 decltype((x)) 와 같이 표현되어 있을 경우, 항상 좌측값 참조를 보고한다.
decltype(x) : int
decltype((x)) : int&
decltype(auto) f1(void)
{
int x = 0;
...
return x; // decltype(x)는 int이므로 f1은 int를 반환
}
decltype(auto) f2(void)
{
int x = 0;
...
return (x); // decltype((x))는 int&이므로 f2는 int&를 반환
}
Item 4. Know how to view deuced types. (추론된 타입을 파악하는 방법을 알아두라)
추론된 타입을 알 수 있는 방법은 총 세가지가 있다.
1. IDE 편집기
2. 컴파일러의 진단 메시지
3. 라이브러리
IDE 편집기는 복잡한 타입에 관여해야할 경우 타입 식별에 그리 도움이 되지 않고,
고의로 오류를 만들어내서 컴파일러의 진단 메시지를 유도하는게 효과적이다.
그러나 라이브러리를 더 선호한다면, 그 중에서도 골라내야 한다.
std::type_info::name 은 템플릿이 추론하는 방식으로 타입 정보를 제공하기 때문에 참조성, 상수성 등등이 무시되는 경우가 많다.
Boost.TypeIndex 라는 라이브러리가 가장 우리가 원하는 결과를 제공한다.
#include <boost/type_index.hpp>
template <typename T>
void f(const T& param)
{
using std::cout;
using boost::typeindex::type_id_with_cvr;
// T를 표시
cout << "T = "
<< type_id_with_cvr<T>().pretty_name()
<< '\n';
// param의 타입을 표시
cout << "param = "
<< type_id_with_cvr<param>().pretty_name()
<< '\n';
...
}
'C++ > Modern Effective C++' 카테고리의 다른 글
[Modern Effective C++] Chap 3. 현대적 C++에 적응하기 (0) | 2020.02.24 |
---|---|
[Modern Effective C++] Chap 2. auto (0) | 2020.02.14 |