변수의 타입 추론( type deduction )
C++ 에는 모든 값에 해당하는 타입이 있습니다.
int main(){
5; // int
3.14; // double
5L; // long
5.0f; // float
?? variable{ 5 }; // variable의 타입 ??
}
예를 들어, 위의 3.14
는 double 타입을 갖고, 5.0f
는 float 타입을 갖고 있습니다.
그렇기 때문에, 프로그래머가 명시하지 않더라도, 컴파일러는 이를 이용해서 변수의 타입 예상할 수 있습니다.
이렇게, 컴파일러가 변수의 값으로부터 변수의 타입을 예상하는 것을 타입 추론( type deduction )이라고 하며, 이 추론의 결과를 이용하기 위해 auto 키워드를 사용합니다.
auto variable{ 5 }; // variable은 int로 추론됨
위에서 컴파일러는 값 5
의 타입이 int
임을 알고 있으므로, variable
의 타입을 int
로 추론합니다.
이렇게, auto 키워드는 변수의 타입을 추론하여 코드를 간결하고 읽기 쉽게 만들어 줄 뿐만 아니라, 이를 수정하는 경우에도 간편하게 만들어 줍니다.
struct Plain_Coordinate{
int x{};
int y{};
};
struct Polar_Coordinate{
double radious{};
double theta{};
};
int main(){
Plain_Coordinate pt;
auto pt2{ pt }; // pt2는 Plane_Coordinate 타입으로 추론됨
auto pt3{ pt2 };
}
위에서, pt
의 타입이 Plane_Coordinate
타입이므로, 컴파일러는 pt2
의 타입을 Plane_Coordinate
타입으로 예상합니다.
이를 Plane_Coordinate pt2{ pt };
로 정의하는 것보다, auto pt2{ pt };
로 정의하는 것이 일반적으로 읽기 쉽습니다.
게다가, pt
의 타입을 Polar_Coordinate
타입으로 변경할 경우, 위에서는 pt
를 정의하는 문장만을 수정해도, 다른 모든 변수의 정의를 수정하게 되는 효과를 얻을 수 있습니다.
int main(){
Polar_Coordinate pt;
auto pt2{ pt }; // pt2는 Polar_Coordinate 타입으로 추론됨
auto pt3{ pt2 };
}
C++ 에는 다양한 값들과 그만큼의 타입이 있습니다.
여기서는 이러한 값들로부터 C++은 변수의 타입을 어떻게 추론하는지 알아보겠습니다.
표현식의 값으로 추론
C++은 표현식을 평가하여, 평가된 값의 타입으로 변수의 타입을 추론합니다.
int add( int x, int y){
return x + y;
}
void empty(){
// do something
}
int main(){
auto var1{ 1 + 2 }; // int로 추론
auto var2{ 1 + 3.14 }; // double로 추론
auto var3{ add( var1, var2 ) }; // int로 추론
auto var4; // error
auto var5{}; // error
auto var6{ empty() }; // error. 반환값이 없음
}
위의 var3
와 같은 경우, 함수 add의 반환값을 평가하여 변수의 타입( 여기선 int
타입 )을 정합니다.
그렇기 때문에, var4
의 정의와 같이 초기값을 지정하지 않거나, var5
와 var6
과 같이 표현식의 값을 평가할 수 없는 경우는 오류입니다.
문자열
C-style 문자열 리터럴( literal )의 타입은 const char 배열 타입입니다.
#include <string_view> // for std::string_view
int main(){
auto str{ "This is string" }; // const char* 타입
using namespace std::string_literals; // for string 리터럴
using namespace std::string_view_literals; // for string_view 리터럴
auto str2{ "This is C++ string"s }; // std::string 타입
auto str3{ "This is C++ string_view"sv }; // std::string_view 타입
}
위에서 "This is string"의 타입은 const char[15]
입니다. ( C-style 문자열의 마지막은 '\0'
로 끝납니다. )
[C++] C-style 문자열의 특징
C-style 문자열C++을 처음 배울 때, 대부분 아래와 같은 예문을 보게 됩니다.#include int main(){ std::cout 그리고, 이 예문에 사용된 "Hello, world!"가 C-style 문자열입니다. 이것을 좀 더 정확하게 정의하자
codingbonfire.tistory.com
그렇지만, 이 타입을 함수에 전달하게 되면 인자의 복사 문제나, 배열의 크기에 따른 매개변수 타입 문제가 발생하기 때문에, C++에서는 이 타입을 암시적으로 const char*
타입으로 전환합니다.
이런 관계로, 컴파일러는 C-stype 문자열을 const char*
타입으로 추론합니다.
그리고, C++은 std::string
타입의 문자열( s
접미사를 붙여 표시 )과 std::string_view
타입의 문자열( sv
접미사를 붙여 표시 )도 지원하는데, 이 경우 컴파일러는 각각의 타입으로 추론합니다.
[C++] 문자열을 읽기 전용으로 참조하는 std::string_view
std::string_viewstd::string_view는 문자열을 사용하면서 발생하는, 무거운 복사 과정을 줄이고자 만든 클래스입니다. C++ 17 이상의 버전에서 사용할 수 있는, 이 클래스는 실제 원본 문자열을 읽기 전용
codingbonfire.tistory.com
const와 constexpr
const 키워드를 auto와 같이 사용하여, 변수를 const 타입으로 추론하도록 만들 수 있습니다.
그리고, 이러한 방식은 constexpr 키워드를 사용할 때도 동일합니다.
int main(){
int var{ 5 };
const auto const_var{ var }; // const int 타입
constexpr auto constexpr_var{ 5 }; // constexpr int 타입
constexpr auto constexpr_var2{ var }; // error !
}
위에서 var
의 타입이 int
이므로, const_var
는 int
타입으로 추론되는데, 앞에 const가 있으므로, 이 변수는 const int
타입으로 추론됩니다.
참고로, constexpr 변수는 상수 표현식( constant expression )으로 초기화되어야 합니다.
[C++] 컴파일 시에 값을 알 수 있는 constexpr 변수
constexpr 변수이전 글에서, 상수 표현식( const expression )은 컴파일 시에 평가할 수 있는 표현식을 말한다고 얘기했었습니다. [C++] 컴파일 시 값을 알 수 있는 상수 표현식( const expression )컴파일 시,
codingbonfire.tistory.com
▼출력
error: the value of 'var' is not usable in a constant expression
그러므로, constexpr_var2
는 constexpr int
타입으로 추론되지만, var
가 상수 표현식이 아니므로 오류입니다.
그런데, 반대로 const 타입으로부터 auto 변수를 추론할 때는, const 속성을 제거하고 추론합니다.
int main(){
const int var{ 5 }; // const 변수
auto var2{ var }; // int 타입
const auto const_var{ var }; // const int 타입
constexpr auto constexpr_var{ var }; // constexpr int 타입
}
위에서 var
의 타입은 const int
타입입니다. ( const는 타입의 일부분입니다. )
그래서, var2
의 타입은 const int
타입일 것 같지만, 컴파일러는 int
타입으로 추론합니다.
이것은 var
가 const 변수라고 해서, var
의 값으로 초기화되는 var2
변수가 반드시 const 변수가 될 이유가 없기 때문입니다.
즉, var
와 var2
변수는 서로 독립적인 객체라는 것입니다.
만약, var2
의 타입을 const 타입으로 추론하고 싶다면, const_var
처럼 auto
앞에 const
를 추가하면 됩니다.
그리고, const auto
와 마찬가지로, constexpr
을 auto
앞에 추가하면 constexpr_var
를 constexpr int
타입으로 추론합니다.
● 참고로, const 정수형 타입( integral type, 예를 들어 const int
) 변수가 상수 표현식( 여기서는 5
)으로 초기화되면, 이 변수 또한 상수 표현식입니다.
따라서, 이전 예제와 달리 constexpr_var
가 상수 표현식 var
로 초기화되므로, 위의 마지막 문장은 합법입니다.
int main(){
const int var{ 5 }; // const 변수
constexpr auto constexpr_var{ var }; // var는 상수 표현식
auto var2{ var }; // int 타입
decltype( var ) var3{ var }; // const int 타입
}
그리고, 위의 decltype은 이름이나 표현식의 구체적인 형식( declared type )을 알려주는 키워드입니다.
[C++] 표현식의 구체적인 타입을 알려주는 decltype
decltype 키워드decltype는 이름이나 표현식의 구체적인 타입( declared type )을 알려주는 키워드입니다.이 키워드는 다음과 같은 규칙에 따라, 표현식의 타입을 반환합니다. ● 표현식이 식별자 또는
codingbonfire.tistory.com
그래서, 변수 var
의 타입이 const int
이므로, decltype( var )
는 const int
타입이 됩니다.
그런데, 위의 마지막 문장같이 decltype을 사용해서 var3
를 정의하려면, var
를 두 번 중복해서 사용해야 합니다.
이 경우는 var3
를 초기화하는 var
로부터 구체적인 타입을 구하는 것이므로, 이러한 경우에도 auto를 사용할 수 있습니다.
const int var{ 5 };
decltype( auto ) var3 { var }; // const int 타입
컴파일러는 위의 decltype( auto )를 변수의 초기값 var
의 구체적인 타입 const int
타입으로 추론합니다.
constexpr은 const와 달리 타입의 일부분이 아닙니다.
그렇지만, constexpr 타입은 암시적으로 const 타입이므로, constexpr 타입의 변수로부터 변수의 타입을 추론하면, const
와 마찬가지로 constexpr
을 제외한 타입으로 추론합니다.
int main(){
constexpr int var{ 5 }; // constexpr 변수
auto var2{ var }; // int 타입
const auto const_var{ var }; // const int 타입
constexpr auto constexpr_var{ var }; // constexpr int 타입
}
그래서, var
의 타입은 const int
타입이지만( constexpr int
타입이 아닙니다. ), var2
의 타입은 int
로 추론됩니다.
만약, const나 constexpr 변수로 추론하고 싶다면, 위와 같이 const
나 constexpr
을 추가해야 합니다.
포인터( pointer )
포인터는 이것이 가리키는 대상이 따로 있다는 점에서 참조( reference )와 비슷하지만, 이 포인터 자체는 변수라는 점을 잊으면 안 됩니다.
따라서, 컴파일러는 다른 변수와 같은 방식으로 포인터의 타입을 추론합니다.
int main(){
int var{ 5 };
auto var2{ var }; // int 타입
int* pVar{ &var };
auto var3{ pVar }; // int* 타입
const int* pcVar{ pVar };
auto var4{ pcVar }; // const int* 타입
}
위에서, var2
를 var
의 타입인 int
로 추론하는 것과 같이, pVar
의 타입이 int*
타입이므로, var3
의 타입을 int*
타입으로 추론합니다.
그리고, const int*
와 같이, const 객체를 대상으로 하는 포인터( pointer to const )는 일반 포인터에 대입할 수 없습니다.
[C++] const 포인터( pointer )의 종류
포인터의 상수성( constness )포인터( pointer )는 객체의 주소를 값으로 하는 변수입니다.그리고, 이 포인터는 역참조( dereference ) 연산자 *를 사용하여, 저장하고 있는 주소에 있는 객체의 값을 변경
codingbonfire.tistory.com
따라서, 컴파일러는 var4
의 타입을 int*
타입이 아니라 const int*
타입으로 추론해야 합니다.
하지만, const 포인터는, 위에서 설명한 const 변수로부터 타입을 추론할 때와 마찬가지로, const 속성을 제거하고 추론합니다.
int main(){
int var{ 5 };
int* const pcVar{ &var };
auto var5{ pcVar}; // int* 타입
const auto cvar5{ pcVar }; // int* const 타입
auto const cvar5_1{ pcVar }; // int* const 타입
const int* const pcVarToConst{ &var };
auto var6{ pcVarToConst }; // const int* 타입
}
위에서, pcVar
가 int* const
타입의 const 포인터라고 해서, var5
가 const 포인터가 되어야 할 필요는 없습니다.
따라서, var5
의 타입을 int*
타입으로 추론합니다.
만약, var5
의 타입을 const 포인터인 int* const
타입으로 추론하고 싶다면, cvar5
처럼 앞에 const
를 붙이면 됩니다.
이때, auto const
와 같이 const
를 뒤에 붙여도 마찬가지가 되는데, 이것은 int 타입으로 추론되는 변수 앞/뒤에 const
를 붙이는 것을 떠올리면 쉽게 이해할 수 있습니다.
int var{ 5 };
const auto cvar{ var }; // const int 타입
auto const cvar_1{ var }; // const int 타입
그리고, 대상이 const 객체인 const 포인터 pcVarToConst
로부터 추론되는 var6
의 타입은, 대상이 const 객체인 포인터 타입( pointer to const )인 const int*
입니다.
이것은, const int* const
타입에서 뒤의 const
속성만 제거된 타입입니다.
auto*
auto 변수는 다양한 타입의 값으로 초기화될 수 있습니다.
하지만, auto* 변수는 포인터 타입으로만 초기화되어야 합니다.
따라서, auto* 형태의 변수를 정의하는 것으로 이 변수가 포인터( pointer )라는 것을 강조할 수 있습니다.
int main(){
int var{ 5 };
auto var2{ var }; // int 타입
auto var3{ &var }; // int* 타입
auto* var4{ var }; // error. 포인터 타입만 가능
auto* var5{ &var }; // int* 타입
}
▼출력
error: unable to deduce 'auto*' from 'var'
위에서, var2
같은 경우 int
타입의 값이든, 포인터 타입의 값이든 주어진 값으로부터 var2
의 타입을 추론할 수 있습니다.
하지만, var4
와 같은 auto* 타입의 변수는 포인터 타입의 값으로만 초기화되어야 합니다.
따라서, int 타입인 var
로 var4
를 초기화하는 문장은 오류입니다.
그리고, const 객체를 가리키는 포인터( pointer to const )로부터 추론할 수 있는 auto*의 타입은 오직 하나뿐입니다.
int main(){
int var{ 5 };
const int* pPtrToConst{ &var }; // const 객체에 대한 포인터
auto* var6{ pPtrToConst }; // const int* 타입
const auto* var7{ pPtrToConst }; // const int* 타입
int* const pConst{ &var }; // const 포인터
auto* var8{ pConst }; // int* 타입
auto* const var9{ pConst }; // int* const 타입
}
위에서, pPtrToConst
같이, const int*
타입의 변수를 대입할 수 있는 포인터 타입은 const int*
뿐입니다.
따라서, var6
의 타입은 const int*
타입이 될 수밖에 없습니다.
var7
의 정의에 사용된 const
는 이 변수가 ( 원래도 그렇지만 한번 더 ) const 객체에 대한 포인터임을 강조하는 것에 불과합니다.
하지만, var8
의 경우, const 포인터의 const 속성은 제거되고 추론되기 때문에, int*
타입으로 추론됩니다.
그러므로, const 포인터로 추론하고 싶다면, var9
와 같이 auto*
뒤에 const
를 추가해주어야 합니다.
참조( reference )
참조는 포인터( pointer ) 같은 변수가 아니라 참조하는 대상에 대한 별명입니다.
그리고, const 변수로부터 초기화되는 변수가 const 타입일 필요가 없는 것과 마찬가지로, 참조로부터 초기화되는 변수가 참조 타입일 필요가 없습니다.
따라서, 컴파일러는 auto를 참조 타입이 아니라, 참조가 가리키는 대상의 타입으로 추론합니다.
int main(){
int var{ 5 };
int& ref{ var };
auto var2{ ref }; // int 타입
const int& const_ref{ var };
auto var3{ const_ref }; // int 타입
}
위에서, 컴파일러는 ref
로 초기화되는 var2
의 타입을 int&
타입이 아니라, int
타입으로 추론합니다.
그리고, const_ref
로 초기화되는 var3
변수도 const 속성과 참조 타입이 아닌 타입으로 추론하므로, int
타입으로 추론됩니다.
따라서, 참조 타입으로 추론하고 싶다면, auto*를 포인터 타입으로 추론하는 것과 같이, auto& 타입을 사용해야 합니다.
int main(){
int var{ 5 };
int& ref{ var };
const int& const_ref{ var };
auto& var4{ ref }; // int& 타입
auto& var5{ const_ref }; // const int& 타입
const auto& var6{ ref }; // const int& 타입
}
위에서 auto&
는 초기화되는 변수의 타입인 참조 타입인 것과 관계없이, 참조 타입으로 추론합니다.
그리고, const 객체로 초기화될 수 있는 참조 타입은 const 참조 타입뿐입니다.
따라서, const_ref
로 초기화되는 var5
의 타입은 const int&
타입입니다.
만약, 초기화되는 변수가 const 참조 타입을 강조하고 싶다면, var6
의 정의와 같이 auto
앞에 const
를 붙이면 됩니다.
정리
- 컴파일러는 변수가 초기화되는 값을 통해 auto의 타입을 추론합니다.
- const 객체로 초기화되는 auto 변수는 반드시 const 객체일 필요가 없으므로, 컴파일러는 이 변수의 타입을 const 속성이 제거된 타입으로 추론합니다.
- auto 변수가 포인터( pointer ) 타입임을 강조하고 싶다면 auto* 타입으로 추론해야 합니다.
- 참조( reference ) 타입으로 초기화되는 auto 변수는 참조 타입일 필요가 없으므로, 컴파일러는 이 변수의 타입을 참조 타입이 아닌 타입으로 추론합니다.
- auto 변수를 참조 타입으로 추론하고 싶다면, auto& 타입으로 추론해야 합니다.
이 글과 관련 있는 글들
함수의 타입 추론과 후행 반환 타입을 위한 auto 키워드