타입
타입 (Type)
객체, 참조, 함수(함수 템플릿 특수화 포함)와 표현식은 **타입(type)**이라는 속성을 가져요. 타입은 그 엔티티에 허용되는 연산을 제한하고, 그 외에는 무의미한 비트 나열에 의미를 부여해요. C++에서 "이 값은 정수다", "이건 포인터다" 같은 분류가 왜 중요한지, 그리고 그 분류가 어떤 기준으로 나뉘는지를 정리해 볼게요.
출처: cppreference
본문
타입 분류
C++ 타입 시스템은 다음 타입들로 이루어져요.
- 기본 타입(fundamental types,
std::is_fundamental참고):void타입(std::is_void참고)std::nullptr_t타입(std::is_null_pointer참고)(since C++11)- std::meta::info 타입(
std::is_reflection참고)(since C++26) - 산술 타입(arithmetic types,
std::is_arithmetic참고):- 정수 타입(integral types, cv-한정 버전 포함,
std::is_integral참고. 정수 타입과 같은 뜻으로 integer type이라고도 함):bool타입- 문자 타입(character types):
- 좁은 문자 타입(narrow character types):
- 보통 문자 타입(ordinary character types):
char,signed char,unsigned char¹ char8_t타입(since C++20)
- 보통 문자 타입(ordinary character types):
- 넓은 문자 타입(wide character types):
char16_t,char32_t(since C++11),wchar_t
- 좁은 문자 타입(narrow character types):
- 부호 있는 정수 타입(signed integer types):
- 표준 부호 있는 정수 타입:
signed char,short,int,long,long long(since C++11) - 확장 부호 있는 정수 타입(구현 정의)
(since C++11)
- 표준 부호 있는 정수 타입:
- 부호 없는 정수 타입(unsigned integer types):
- 표준 부호 없는 정수 타입:
unsigned char,unsigned short,unsigned,unsigned long,unsigned long long(since C++11) - 확장 부호 없는 정수 타입(각각 확장 부호 있는 정수 타입에 대응하고 그 반대도 마찬가지)
(since C++11)
- 표준 부호 없는 정수 타입:
- 부동소수점 타입(floating-point types,
std::is_floating_point참고):- 표준 부동소수점 타입:
float,double,long double및 그 cv-한정 버전 - 확장 부동소수점 타입(cv-한정 버전 포함):
- 고정 폭 부동소수점 타입
- 기타 구현 정의 확장 부동소수점 타입
(since C++23)
- 표준 부동소수점 타입:
- 정수 타입(integral types, cv-한정 버전 포함,
- 복합 타입(compound types,
std::is_compound참고):- 참조 타입(reference types,
std::is_reference참고):- lvalue 참조 타입(
std::is_lvalue_reference참고):- 객체 타입에 대한 lvalue 참조
- 함수 타입에 대한 lvalue 참조
- rvalue 참조 타입(
std::is_rvalue_reference참고):(since C++11)- 객체 타입에 대한 rvalue 참조
- 함수 타입에 대한 rvalue 참조
- lvalue 참조 타입(
- 포인터 타입(pointer types,
std::is_pointer참고):- 객체 포인터 타입(object pointer types):
- 객체에 대한 포인터 타입
void에 대한 포인터 타입
- 함수에 대한 포인터 타입
- 멤버 포인터 타입(
std::is_member_pointer참고):- 데이터 멤버에 대한 포인터 타입(
std::is_member_object_pointer참고) - 멤버 함수에 대한 포인터 타입(
std::is_member_function_pointer참고)
- 데이터 멤버에 대한 포인터 타입(
- 객체 포인터 타입(object pointer types):
- 배열 타입(array types,
std::is_array참고) - 함수 타입(function types,
std::is_function참고) - 열거형 타입(enumeration types,
std::is_enum참고):- 스코프 없는 열거형 타입
- 스코프 있는 열거형 타입(
std::is_scoped_enum참고)(since C++11)
- 클래스 타입(class types):
- 비공용체 타입(
std::is_class참고) - 공용체 타입(
std::is_union참고)
- 비공용체 타입(
- 참조 타입(reference types,
↑signed char와 unsigned char는 좁은 문자 타입이지만 문자 타입(character type)은 아니에요. 다시 말해 좁은 문자 타입의 집합은 문자 타입 집합의 부분집합이 아니에요.
참조·함수를 제외한 모든 cv-한정이 없는 타입에 대해, 타입 시스템은 그 타입의 cv-한정 버전 세 가지(const, volatile, const volatile)를 추가로 지원해요.
기타 분류
객체 타입(object type, std::is_object 참고)은 (cv-한정일 수 있는) 타입으로, 함수 타입도 참조 타입도 (cv-한정일 수 있는) void도 아닌 타입이에요.
다음 타입들을 통틀어 스칼라 타입(scalar types, std::is_scalar 참고)이라 해요.
- 산술 타입
- 열거형 타입
- 포인터 타입
- 멤버 포인터 타입
std::nullptr_t(since C++11)- std::meta::info
(since C++26) - 이 타입들의 cv-한정 버전
다음 타입들을 통틀어 암시적 수명 타입(implicit-lifetime types)이라 해요.
- 스칼라 타입
- 암시적 수명 클래스 타입
- 배열 타입
- 이 타입들의 cv-한정 버전
| 다음 타입들을 통틀어 자명 복사 가능 타입(trivially copyable types)이라 해요. |
|---|
| 스칼라 타입, 자명 복사 가능 클래스 타입, 그런 타입의 배열, 이 타입들의 cv-한정 버전 |
다음 타입들을 통틀어 표준 레이아웃 타입(standard-layout types)이라 해요. (since C++11) |
| 스칼라 타입, 표준 레이아웃 클래스 타입, 그런 타입의 배열, 이 타입들의 cv-한정 버전 |
다음 타입들을 통틀어 POD 타입(std::is_pod 참고)이라 해요. |
|---|
| 스칼라 타입, POD 클래스, 그런 타입의 배열, 이 타입들의 cv-한정 버전 |
다음 타입들을 통틀어 자명 타입(trivial types, std::is_trivial 참고)이라 해요. |
| 스칼라 타입, 자명 클래스 타입, 그런 타입의 배열, 이 타입들의 cv-한정 버전 |
프로그램 정의 타입
프로그램 정의 특수화(program-defined specialization)는 C++ 표준 라이브러리의 일부가 아니고 구현이 정의하지도 않은 명시적 특수화 또는 부분 특수화예요.
프로그램 정의 타입(program-defined type)은 다음 중 하나예요.
- C++ 표준 라이브러리의 일부가 아니고 구현이 정의하지도 않은,
non-closure(since C++11)클래스 타입 또는 열거형 타입 - 구현이 제공하지 않은 람다 표현식의 클로저 타입
(since C++11) - 프로그램 정의 특수화의 인스턴스화
타입 이름 붙이기
이름은 다음 방법으로 타입을 가리키도록 선언될 수 있어요.
- 클래스 선언
- 공용체 선언
- enum 선언
- typedef 선언
- 타입 별명 선언
이름이 없는 타입은 C++ 프로그램에서 자주 가리켜야 할 필요가 있는데, 그에 쓰이는 문법을 type-id라고 해요. 타입 T를 가리키는 type-id의 문법은 타입 T의 변수나 함수 선언의 문법에서 식별자를 뺀 것과 정확히 같아요. 단, 선언 문법의 decl-specifier-seq이 type-specifier-seq으로 제한되고, type-id가 비템플릿 타입 별명 선언의 오른쪽에 나타날 때만 새 타입을 정의할 수 있어요.
int* p; // declaration of a pointer to int
static_cast<int*>(p); // type-id is "int*"
int a[3]; // declaration of an array of 3 int
new int[3]; // type-id is "int[3]" (called new-type-id)
int (*(*x[2])())[3]; // declaration of an array of 2 pointers to functions
// returning pointer to array of 3 int
new (int (*(*[2])())[3]); // type-id is "int (*(*[2])())[3]"
void f(int); // declaration of a function taking int and returning void
std::function<void(int)> x = f; // type template parameter is a type-id "void(int)"
std::function<auto(int) -> void> y = f; // same
std::vector<int> v; // declaration of a vector of int
sizeof(std::vector<int>); // type-id is "std::vector<int>"
struct { int x; } b; // creates a new type and declares an object b of that type
sizeof(struct { int x; }); // error: cannot define new types in a sizeof expression
using t = struct { int x; }; // creates a new type and declares t as an alias of that type
sizeof(static int); // error: storage class specifiers not part of type-specifier-seq
std::function<inline void(int)> f; // error: neither are function specifiers
선언 문법에서 이름을 뺀 declarator 부분을 **추상 선언자(abstract-declarator)**라고 불러요.
type-id는 다음 상황에서 쓸 수 있어요.
- 캐스트 표현식에서 대상 타입을 지정할 때
sizeof,alignof,alignas,new,typeid의 인자로- 타입 별명 선언의 오른쪽에
- 함수 선언의 후행 반환 타입으로
- 템플릿 타입 매개변수의 기본 인자로
- 템플릿 타입 매개변수의 템플릿 인자로
- 동적 예외 명세에서
(until C++17)
type-id는 약간의 수정과 함께 다음 상황에서 쓸 수 있어요.
- 함수의 매개변수 목록에서(매개변수 이름이 생략될 때) type-id는
type-specifier-seq대신decl-specifier-seq을 써요(특히 일부 저장 클래스 지정자가 허용돼요) - 사용자 정의 변환 함수의 이름에서는 추상 선언자에 함수·배열 연산자가 포함될 수 없어요
| 이 절은 불완전해요 | |
|---|---|
| 이유: 8.2 [dcl.ambig.res]를 간결하게 요약할 수 있다면 |
| 이 절은 불완전해요 | |
|---|---|
| 이유: decltype과 auto를 언급하고 링크하기 |
정교화된 타입 지정자 (Elaborated type specifier)
정교화된 타입 지정자는 이전에 선언된 클래스 이름(class, struct, union)이나 enum 이름을, 그 이름이 비타입 선언에 의해 가려졌더라도 가리키는 데 쓸 수 있어요. 새 클래스 이름을 선언하는 데도 쓸 수 있죠.
자세한 내용은 정교화된 타입 지정자 문서를 보세요.
정적 타입 (Static type)
프로그램의 컴파일 시점 분석에서 얻어지는 표현식의 타입을 그 표현식의 정적 타입이라 해요. 정적 타입은 프로그램이 실행되는 동안 변하지 않아요.
동적 타입 (Dynamic type)
어떤 glvalue 표현식이 다형적 객체를 가리킨다면, 그 가장 파생된 객체의 타입을 동적 타입이라고 해요.
// given
struct B { virtual ~B() {} }; // polymorphic type
struct D : B {}; // polymorphic type
D d; // most-derived object
B* ptr = &d;
// the static type of (*ptr) is B
// the dynamic type of (*ptr) is D
prvalue 표현식에서는 동적 타입이 항상 정적 타입과 같아요.
불완전 타입 (Incomplete type)
다음 타입들은 불완전 타입이에요.
- (cv-한정일 수 있는)
void타입 - 불완전하게 정의된 객체 타입:
- (전방 선언으로) 선언됐지만 정의되지 않은 클래스 타입
- 알 수 없는 경계의 배열
- 불완전 타입의 요소로 이루어진 배열
- 선언 지점부터 기반 타입이 결정될 때까지의 열거형 타입
그 외의 모든 타입은 완전 타입이에요.
다음 문맥 중 하나는 타입 T가 완전할 것을 요구해요.
- 반환 타입
T또는 인자 타입T인 함수의 정의 또는 호출 - 타입
T의 객체 정의 - 타입
T의 비정적 클래스 데이터 멤버 선언 - 타입
T의 객체 또는 요소 타입이T인 배열에 대한 new 표현식 - 타입
T의 glvalue에 적용되는 lvalue-to-rvalue 변환 - 타입
T로의 암시적 또는 명시적 변환 - 널 포인터 상수나 (cv-한정일 수 있는)
void에 대한 포인터에서 변환되는 경우를 제외한,T*또는T&로의 표준 변환·dynamic_cast·static_cast - 타입
T의 표현식에 적용되는 클래스 멤버 접근 연산자 - 타입
T에 적용되는typeid,sizeof,alignof연산자 T에 대한 포인터에 적용되는 산술 연산자- 기반 클래스
T를 가진 클래스의 정의 - 타입
T의 lvalue에 대한 대입 - 타입
T,T&,T*의 핸들러
(일반적으로 T의 크기와 레이아웃을 알아야 하는 상황이에요.)
이 상황 중 하나라도 번역 단위에서 발생하면 타입의 정의가 같은 번역 단위에 나타나야 해요. 그렇지 않으면 요구되지 않아요.
불완전하게 정의된 객체 타입은 완성될 수 있어요.
- 클래스 타입(
class X처럼)은 번역 단위의 한 지점에서 불완전으로 간주되고 나중에 완전으로 간주될 수 있어요. 그 두 지점에서 타입class X는 같은 타입이에요.
struct X; // declaration of X, no definition provided yet
extern X* xp; // xp is a pointer to an incomplete type:
// the definition of X is not reachable
void foo()
{
xp++; // ill-formed: X is incomplete
}
struct X { int i; }; // definition of X
X x; // OK: the definition of X is reachable
void bar()
{
xp = &x; // OK: type is "pointer to X"
xp++; // OK: X is complete
}
- 배열 객체의 선언된 타입은 불완전 클래스 타입의 배열일 수 있어서 그로 인해 불완전할 수 있어요. 클래스 타입이 나중에 번역 단위에서 완성되면 배열 타입도 완전해져요. 그 두 지점의 배열 타입은 같은 타입이에요.
- 배열 객체의 선언된 타입은 알 수 없는 경계의 배열이라서 한 지점에서 불완전하고 나중에 완전해질 수 있어요. 그 두 지점의 배열 타입("
T의 알 수 없는 경계의 배열"과 "N개의T배열")은 서로 다른 타입이에요.
알 수 없는 경계의 배열에 대한 포인터나 참조의 타입은 영구적으로 불완전 타입을 가리켜요. typedef 선언으로 이름 붙은 알 수 없는 경계의 배열도 영구적으로 불완전 타입을 가리켜요. 두 경우 모두 배열 타입은 완성될 수 없어요.
extern int arr[]; // the type of arr is incomplete
typedef int UNKA[]; // UNKA is an incomplete type
UNKA* arrp; // arrp is a pointer to an incomplete type
UNKA** arrpp;
void foo()
{
arrp++; // error: UNKA is an incomplete type
arrpp++; // OK: sizeof UNKA* is known
}
int arr[10]; // now the type of arr is complete
void bar()
{
arrp = &arr; // OK: qualification conversion (since C++20)
arrp++; // error: UNKA cannot be completed
}
더 알아보기
- 타입 특성(type traits)은 타입의 속성을 질의하는 컴파일 시점 템플릿 기반 인터페이스예요. type_traits 라이브러리에서 확인할 수 있어요.
- 기본 타입의 자세한 분류와 크기는 기본 타입 문서에서 볼 수 있어요.
- cppreference의 타입 원문에서 결함 보고(defect report) 기록을 더 볼 수 있어요.