보통 산술 변환(Usual arithmetic conversions)

보통 산술 변환(Usual arithmetic conversions)

산술 타입이나 열거형 타입의 피연산자를 기대하는 이항 연산자를 많이 써 보면, intdouble을 더할 때처럼 서로 다른 타입이 만나면 자동으로 변환이 일어난다는 걸 느껴요. 이때 양쪽 피연산자를 어떤 공통 타입으로 맞추는 규칙이 필요한데, 이것이 바로 **보통 산술 변환(usual arithmetic conversions)**이에요. 같은 방식으로 변환과 결과 타입을 만들어 내고, 그 결과 타입이 곧 연산의 결과 타입이 돼요.

출처: cppreference

본문

정의(Definition)

보통 산술 변환은 다음과 같이 정의돼요.

1단계(Stage 1)

두 피연산자 모두에 lvalue-to-rvalue 변환을 적용해요. 그 결과로 생긴 prvalue가 나머지 과정에서 원래 피연산자 자리를 대신해요.

2단계(Stage 2)

  • 두 피연산자 중 하나가 스코프 있는 열거형 타입이면 변환을 수행하지 않아요. 다른 피연산자가 같은 타입이 아니면 표현식은 잘못된 형태(ill-formed)예요.
  • 그렇지 않으면 다음 단계로 진행해요. (C++11부터)

3단계(Stage 3)

  • 두 피연산자 중 하나가 열거형 타입이고, 다른 피연산자가 다른 열거형 타입이나 부동소수점 타입이면 표현식은 잘못된 형태예요.
  • 그렇지 않으면 다음 단계로 진행해요. (C++26부터)

4단계(Stage 4)

  • 두 피연산자 중 하나가 부동소수점 타입이면 다음 규칙을 적용해요.
    • 두 피연산자가 같은 타입이면 더 이상 변환을 수행하지 않아요.
    • 그렇지 않고 두 피연산자 중 하나가 부동소수점이 아닌 타입이면, 그 피연산자를 다른 피연산자의 타입으로 변환해요.
    • 그렇지 않고 두 피연산자 타입의 부동소수점 변환 순위가 정렬되어 있고(since C++23) 같지 않으면, 부동소수점 변환 순위가 더 낮은 타입의 피연산자를 다른 피연산자의 타입으로 변환해요.
    • 그렇지 않고 두 피연산자 타입의 부동소수점 변환 순위가 같으면, 부동소수점 변환 하위 순위(subrank)가 더 낮은 피연산자를 다른 피연산자의 타입으로 변환해요.
    • 그렇지 않으면 표현식은 잘못된 형태예요. (C++23부터)
  • 그렇지 않으면 두 피연산자 모두 정수 타입이므로 다음 단계로 진행해요.

5단계(Stage 5)

두 피연산자는 공통 타입 C로 변환돼요. T1T2를 (정수 승격 규칙에 따른) 피연산자의 승격된 타입이라 할 때, C를 정하려면 다음 규칙을 적용해요.

  • T1T2가 같은 타입이면 C는 그 타입이에요.
  • 그렇지 않고 T1T2가 모두 부호 있는 정수 타입이거나 모두 부호 없는 정수 타입이면, C는 정수 변환 순위가 더 큰 타입이에요.
  • 그렇지 않으면 T1T2 중 하나는 부호 있는 정수 타입 S, 다른 하나는 부호 없는 정수 타입 U예요. 다음 규칙을 적용해요.
    • U의 정수 변환 순위가 S의 정수 변환 순위보다 크거나 같으면 CU예요.
    • 그렇지 않고 SU의 모든 값을 표현할 수 있으면 CS예요.
    • 그렇지 않으면 CS에 대응하는 부호 없는 정수 타입이에요.

한 피연산자가 열거형 타입이고 다른 피연산자가 다른 열거형 타입이나 부동소수점 타입이면 이 동작은 비권장(deprecated)이에요. (C++20부터, C++26까지)

정수 변환 순위(Integer conversion rank)

모든 정수 타입은 다음과 같이 정의되는 정수 변환 순위를 가져요.

  • charsigned char(char가 부호 있으면)를 제외한 두 부호 있는 정수 타입은 표현이 같아도 같은 순위를 가지지 않아요.
  • 부호 있는 정수 타입의 순위는 폭이 더 작은 부호 있는 정수 타입의 순위보다 커요.
  • 다음 정수 타입들의 순위는 순서대로 감소해요.
    • long long (C++11부터)
    • long
    • int
    • short
    • signed char
  • 부호 없는 정수 타입의 순위는 대응하는 부호 있는 정수 타입의 순위와 같아요.
  • 모든 표준 정수 타입의 순위는 같은 폭의 확장 정수 타입의 순위보다 커요. (C++11부터)
  • bool의 순위는 모든 표준 정수 타입의 순위보다 작아요.
  • 부호화 문자 타입(char, char8_t(C++20부터), char16_t, char32_t(C++11부터), wchar_t)의 순위는 각자의 기본 타입의 순위와 같아요. 즉:
    • char의 순위는 signed charunsigned char의 순위와 같아요.
    • char8_t의 순위는 unsigned char의 순위와 같아요.
    • char16_t의 순위는 std::uint_least16_t의 순위와 같아요.
    • char32_t의 순위는 std::uint_least32_t의 순위와 같아요.
  • wchar_t의 순위는 구현 정의인 기본 타입의 순위와 같아요.
  • 같은 폭인 다른 확장 부호 있는 정수 타입에 대한 확장 부호 있는 정수 타입의 순위는 구현 정의지만, 정수 변환 순위를 정하는 다른 규칙의 적용을 받아요. (C++11부터)
  • 모든 정수 타입 T1, T2, T3에 대해, T1T2보다 큰 순위를 갖고 T2T3보다 큰 순위를 가지면 T1T3보다 큰 순위를 가져요.

정수 변환 순위는 정수 승격(integral promotion)의 정의에도 쓰여요.

부동소수점 변환 순위와 하위 순위(Floating-point conversion rank and subrank)

부동소수점 변환 순위

모든 부동소수점 타입은 다음과 같이 정의되는 부동소수점 변환 순위를 가져요.

  • long double
  • double
  • float
  • 실수 타입 T의 순위는 그 값 집합이 T의 값 집합의 진부분집합인 실수 타입의 순위보다 커요.
  • 값 집합이 같은 두 확장 부동소수점 타입은 같은 순위를 가져요.
  • 정확히 하나의 cv-비한정 표준 부동소수점 타입과 같은 값 집합을 가진 확장 부동소수점 타입은 그 표준 부동소수점 타입과 같은 순위를 가져요.
  • 둘 이상의 cv-비한정 표준 부동소수점 타입과 같은 값 집합을 가진 확장 부동소수점 타입은 double과 같은 순위를 가져요. (C++23부터)

부동소수점 변환 하위 순위

같은 부동소수점 변환 순위를 가진 부동소수점 타입들은 부동소수점 변환 하위 순위로 정렬돼요. 하위 순위는 같은 순위를 가진 타입들 사이에서 전순서(total order)를 이뤄요.

std::float16_t, std::float32_t, std::float64_t, std::float128_t(고정 폭 부동소수점 타입)는 같은 변환 순위를 가진 표준 부동소수점 타입보다 큰 변환 하위 순위를 가져요. 그 외의 경우 변환 하위 순위 순서는 구현 정의예요. (C++23부터)

활용(Usage)

부동소수점 변환 순위와 하위 순위는 다음에도 쓰여요.

  • 서로 다른 부동소수점 타입 사이의 변환이 암시적일 수 있는지, 아니면 좁히는 변환(narrowing conversion)인지 결정
  • 오버로드 해석에서 변환 시퀀스 구별
  • std::basic_istream::operator>>를 사용한 확장 부동소수점 타입 추출에서 std::num_get::get()이 실제로 추출하는 타입 결정
  • std::basic_ostream::operator<<를 사용한 확장 부동소수점 타입 삽입에서 std::num_put::put()이 실제로 삽입하는 타입 결정 (C++23부터)
  • std::complex의 변환 생성자가 explicit인지 결정
  • 서로 다른 부동소수점 타입의 인자를 공용 또는 특수 수학 함수에 전달할 때 공통 부동소수점 타입 결정

결함 보고(Defect reports)

이전에 발표된 C++ 표준들에 소급 적용된 동작 변경 결함 보고들이 있어요.

DR 적용 대상 발표 당시 동작 올바른 동작
CWG 1642 C++98 보통 산술 변환이 lvalue를 포함할 수 있었음 먼저 lvalue-to-rvalue 변환을 적용
CWG 2528 C++20 unsigned charunsigned int의 삼자 비교는 중간 정수 승격 때문에 잘못된 형태였음[1] 실제로 피연산자를 승격하지 않고, 승격된 타입에 기반해 공통 타입을 결정[2]
CWG 2892 C++98 두 피연산자가 같은 부동소수점 타입일 때 "더 이상 변환이 필요 없다"는 의미가 불분명했음 "더 이상 변환을 수행하지 않는다"로 변경
  • ↑ 해결 전에는 unsigned char가 5단계 시작에서 int로 승격된 뒤 unsigned int로 변환됐어요. 그런데 후자의 변환은 좁히는 변환이라 삼자 비교가 잘못된 형태가 됐어요.
  • ↑ 해결 후에도 공통 타입은 여전히 unsigned int예요. 차이점은 unsigned char가 중간 정수 승격 없이 unsigned int로 직접 변환된다는 거예요. 이 변환은 좁히는 변환이 아니므로 삼자 비교는 잘 형성돼요.

더 알아보기

  • 정수 승격: 작은 정수 타입을 int 이상으로 올리는 변환.
  • 암시적 변환: 타입이 자동으로 맞춰지는 변환 전반.
  • 산술 연산자: 이 변환이 적용되는 주요 연산자들.