Implicit conversions
암시적 변환은 특정 타입
T1
의 표현식이 해당 타입을 허용하지 않지만 다른 타입
T2
를 허용하는 컨텍스트에서 사용될 때 수행됩니다; 특히:
-
표현식이
T2를 매개변수로 선언된 함수 호출 시 인수로 사용될 때; -
표현식이
T2를 기대하는 연산자의 피연산자로 사용될 때; -
T2타입의 새 객체를 초기화할 때 (함수가T2를 반환하는 경우return문 포함); -
표현식이
switch
문에서 사용될 때 (
T2는 정수형); -
표현식이
if
문 또는 루프에서 사용될 때 (
T2는 bool ).
프로그램은
T1
에서
T2
로 명확한 하나의
암시적 변환 시퀀스
가 존재할 경우에만 올바른 형태(컴파일 가능)입니다.
호출되는 함수나 연산자에 여러 오버로드가 존재하는 경우,
T1
에서 사용 가능한 각
T2
로의 암시적 변환 시퀀스가 구성된 후,
오버로드 해결
규칙에 따라 컴파일할 오버로드가 결정됩니다.
참고: 산술 표현식에서 이항 연산자의 피연산자에 대한 암시적 변환의 대상 유형은 별도의 규칙 집합에 의해 결정됩니다: usual arithmetic conversions .
목차 |
변환 순서
암시적 변환 순서는 다음과 같은 순서로 구성됩니다:
생성자 또는 사용자 정의 변환 함수의 인수를 고려할 때, 오직 하나의 표준 변환 시퀀스만 허용됩니다 (그렇지 않으면 사용자 정의 변환이 효과적으로 연쇄될 수 있기 때문입니다). 비클래스 타입에서 다른 비클래스 타입으로 변환할 때는 오직 표준 변환 시퀀스만 허용됩니다.
표준 변환 시퀀스는 다음과 같은 순서로 구성됩니다:
- lvalue-to-rvalue conversion ,
- array-to-pointer conversion , 그리고
- function-to-pointer conversion ;
|
3)
0개 또는 1개의
함수 포인터 변환
;
|
(since C++17) |
사용자 정의 변환은 0개 또는 1개의 비-explicit 단일 인수 변환 생성자 또는 비-explicit 변환 함수 호출로 구성됩니다.
표현식
e
가
T2
로 암시적으로 변환 가능하다
고 말하는 것은
T2
가
e
로부터
복사 초기화
될 수 있을 때, 즉 선언문
T2 t
=
e
;
가 (컴파일 가능하도록) 잘 구성되어 있을 때를 의미합니다. 이때
t
는 가상의 임시 객체입니다. 이는
직접 초기화
(
T2 t
(
e
)
)와는 다른데, 직접 초기화에서는 명시적 생성자와 변환 함수가 추가로 고려됩니다.
컨텍스트 변환
|
다음 문맥에서 bool 타입이 기대되며, 선언 bool t ( e ) ; 이 유효할 경우(즉, explicit T :: operator bool ( ) const ; 과 같은 명시적 변환 함수도 고려됨) 암시적 변환이 수행됩니다. 이러한 표현식 e 는 문맥적으로 bool 로 변환된다 고 합니다.
|
(C++11부터) |
다음 문맥에서, 문맥별 타입
T
가 예상되며, 클래스 타입
E
의 표현식
e
는 다음 조건에서만 허용됩니다
|
(until C++14) |
|
(since C++14) |
이러한 표현식
e
은 지정된 타입
T
로
문맥적 암시적 변환
된다고 합니다.
명시적 변환 함수는 고려되지 않으며, 이는
bool
로의 문맥적 변환에서는 고려된다는 점에 유의하십시오.
(C++11부터)
-
delete-표현식의 인수(
T는 임의의 객체 포인터 타입); -
리터럴 클래스가 사용되는
정수형 상수 표현식
(
T는 임의의 정수형 또는 비한정 열거형 타입, 선택된 사용자 정의 변환 함수는 constexpr 이어야 함); -
switch문의 제어 표현식(T는 임의의 정수형 또는 열거형 타입).
#include <cassert> template<typename T> class zero_init { T val; public: zero_init() : val(static_cast<T>(0)) {} zero_init(T val) : val(val) {} operator T&() { return val; } operator T() const { return val; } }; int main() { zero_init<int> i; assert(i == 0); i = 7; assert(i == 7); switch (i) {} // C++14까지 오류 (둘 이상의 변환 함수 존재) // C++14부터 정상 동작 (두 함수 모두 동일한 int 타입으로 변환) switch (i + 0) {} // 항상 정상 동작 (암시적 변환) }
값 변환
값 변환(value transformation)은 표현식의 값 범주 를 변경하는 변환입니다. 표현식이 다른 값 범주의 표현식을 기대하는 연산자의 피연산자로 나타날 때마다 발생합니다:
- glvalue가 피연산자로 사용될 때 해당 피연산자에 prvalue가 필요한 연산자의 경우, lvalue-to-rvalue , array-to-pointer , 또는 function-to-pointer 표준 변환이 적용되어 표현식을 prvalue로 변환합니다.
|
(since C++17) |
Lvalue-to-rvalue 변환
lvalue
(왼값)
(C++11 이전)
glvalue
(일반화된 왼값)
(C++11 이후)
인 비함수(non-function), 비배열(non-array) 타입
T
는 암시적으로
rvalue
(오른값)
(C++11 이전)
prvalue
(순수 오른값)
(C++11 이후)
로 변환될 수 있습니다:
-
만약
T가 클래스 타입이 아닌 경우, rvalue (until C++11) prvalue (since C++11) 의 타입은T의 cv-unqualified 버전입니다. -
그렇지 않은 경우,
rvalue
(until C++11)
prvalue
(since C++11)
의 타입은
T입니다.
만약 프로그램에 의해 불완전한 타입 에서의 lvalue-to-rvalue 변환이 요구된다면, 해당 프로그램은 잘못된 형식입니다.
lvalue (until C++11) glvalue (since C++11) 가 참조하는 객체를 obj 로 가정할 때:
|
(C++11 이전) | ||||
|
(C++11 이후) |
이 변환은 메모리 위치에서 값을 CPU 레지스터로 읽어오는 동작을 모델링합니다.
배열-포인터 변환
lvalue 또는 rvalue 타입인 "T의 N개 배열" 또는 "T의 알려지지 않은 경계 배열"은 "T에 대한 포인터" 타입의 prvalue 로 암시적으로 변환될 수 있습니다. 배열이 prvalue인 경우, temporary materialization 이 발생합니다. (C++17부터) 결과 포인터는 배열의 첫 번째 요소를 참조합니다 (자세한 내용은 Array-to-pointer decay 참조).
함수-포인터 변환
함수 타입의 lvalue 는 해당 함수를 가리키는 prvalue 포인터 로 암시적으로 변환될 수 있습니다. 이는 비정적 멤버 함수를 참조하는 lvalue가 존재하지 않기 때문에 비정적 멤버 함수에는 적용되지 않습니다.
임시 객체 구체화(Temporary materialization)
모든 완전한 타입
struct S { int m; }; int i = S().m; // C++17부터 멤버 접근은 glvalue를 기대함; // S() prvalue가 xvalue로 변환됨 임시 객체 구체화는 다음 상황에서 발생합니다:
동일한 타입의 prvalue로부터 객체를 초기화할 때( 직접 초기화 또는 복사 초기화 )에는 임시 객체 구체화가 발생하지 않습니다 : 이러한 객체는 초기화자로부터 직접 초기화됩니다. 이는 "보장된 복사 생략(guaranteed copy elision)"을 보장합니다. |
(C++17부터) |
정수 승격
prvalues of small integral types (such as char ) and unscoped enumeration types may be converted to prvalues of larger integral types (such as int ). In particular, arithmetic operators do not accept types smaller than int as arguments, and integral promotions are automatically applied after lvalue-to-rvalue conversion, if applicable. This conversion always preserves the value.
이 섹션의 다음 묵시적 변환들은 integral promotions 로 분류됩니다.
주어진 원본 타입에 대해, 정수 승격의 대상 타입은 유일하며, 다른 모든 변환은 승격이 아닙니다. 예를 들어, 오버로드 해결 은 char -> int (승격)을 char -> short (변환)보다 선호합니다.
정수형 타입의 승격
bool 타입의 prvalue는 int 타입의 prvalue로 변환될 수 있으며, false 는 0 이 되고 true 는 1 이 됩니다.
정수형
T
(단,
bool
제외)의 prvalue
val
에 대해:
- val 은 int 가 비트 필드의 모든 값을 표현할 수 있는 경우 int 타입의 prvalue로 변환될 수 있습니다;
- 그렇지 않으면, val 은 unsigned int 가 비트 필드의 모든 값을 표현할 수 있는 경우 unsigned int 로 변환될 수 있습니다;
- 그렇지 않으면, val 은 항목 (3)에 명시된 규칙에 따라 변환될 수 있습니다.
-
만약
T가 char8_t , (C++20부터) char16_t , char32_t 또는 (C++11부터) wchar_t 인 경우, val 은 항목 (3)에 명시된 규칙에 따라 변환될 수 있음; -
그렇지 않고,
T의 정수 변환 순위 가 int 의 순위보다 낮은 경우:
-
-
val
은
int
타입의 prvalue로 변환될 수 있음 (만약
int
가
T의 모든 값을 표현할 수 있는 경우); - 그렇지 않으면, val 은 unsigned int 타입의 prvalue로 변환될 수 있음.
-
val
은
int
타입의 prvalue로 변환될 수 있음 (만약
int
가
T
가 주어진 문자 타입 중 하나인 경우)에서 지정된 경우,
val
은 해당 기본 타입의 모든 값을 표현할 수 있는 다음 타입들 중 첫 번째 타입의 prvalue로 변환될 수 있습니다:
-
- int
- unsigned int
- long
- unsigned long
|
(C++11부터) |
열거형 타입에서의 승격
고정되지 않은 기반 타입을 가진 비범위 열거형 타입의 prvalue는 전체 값 범위를 담을 수 있는 다음 목록의 첫 번째 타입으로 변환될 수 있습니다:
- int
- unsigned int
- long
- unsigned long
|
(C++11부터) |
|
고정된 기반 타입을 가진 비범위 열거형 타입의 prvalue는 해당 기반 타입으로 변환될 수 있습니다. 또한, 기반 타입이 정수 승격의 대상인 경우 승격된 기반 타입으로도 변환될 수 있습니다. 오버로드 해결 의 목적상 승격되지 않은 기반 타입으로의 변환이 더 우선합니다. |
(C++11부터) |
부동소수점 승격
prvalue 형식의 float 는 double 형식의 prvalue로 변환될 수 있습니다. 값은 변경되지 않습니다.
이 변환을 floating-point promotion 이라고 합니다.
숫자 변환
프로모션과 달리, 숫자 변환은 값의 변화를 초래할 수 있으며, 정밀도 손실 가능성이 있습니다.
정수 변환
정수 타입 또는 비범위 열거형 타입의 prvalue 는 다른 어떤 정수 타입으로도 변환될 수 있습니다. 변환이 정수 승금에 해당하는 경우, 이는 변환이 아닌 승금으로 간주됩니다.
-
대상 유형이 부호 없는 경우, 결과 값은 소스 값과 동일한 가장 작은 부호 없는 값으로
모듈로
2
n
입니다. 여기서 n 은 대상 유형을 표현하는 데 사용되는 비트 수입니다.
-
- 즉, 대상 유형이 더 넓은지 좁은지에 따라 부호 있는 정수는 부호 확장 [1] 되거나 잘리고, 부호 없는 정수는 각각 0 확장되거나 잘립니다.
-
대상 유형이 부호 있는 정수인 경우, 소스 정수 값을 대상 유형으로 표현할 수 있으면 값은 변경되지 않습니다. 그렇지 않으면 결과는
구현에서 정의됨
(C++20까지)
소스 값을 대상 유형의 비트 수
2
n
로 나눈 나머지와 같은 대상 유형의 고유 값 (C++20부터) 입니다 ( 부호 있는 정수 산술 오버플로 는 정의되지 않은 동작인 것과 다릅니다). - 소스 유형이 bool 인 경우, false 값은 0으로 변환되고 true 값은 대상 유형의 값 1로 변환됩니다 (대상 유형이 int 인 경우, 이는 정수 변환이 아닌 정수 승격입니다).
- 대상 유형이 bool 인 경우, 이는 부울 변환 입니다 (아래 참조).
- ↑ 이는 정확한 너비 정수 타입 에만 요구되는 2의 보수 연산을 사용하는 경우에만 적용됩니다. 그러나 현재 C++ 컴파일러가 있는 모든 플랫폼은 2의 보수 연산을 사용한다는 점에 유의하십시오.
부동소수점 변환
|
부동소수점 타입의 prvalue 는 다른 어떤 부동소수점 타입의 prvalue로도 변환될 수 있습니다. |
(C++23 이전) |
|
부동소수점 타입의 prvalue 는 부동소수점 변환 순위 가 더 크거나 같은 다른 부동소수점 타입의 prvalue로 변환될 수 있습니다. 표준 부동소수점 타입의 prvalue 는 다른 어떤 표준 부동소수점 타입의 prvalue로도 변환될 수 있습니다.
|
(C++23 이후) |
변환이 부동 소수점 승급에 해당하는 경우, 이는 변환이 아닌 승급입니다.
- 원본 값이 대상 타입으로 정확히 표현될 수 있는 경우, 값은 변경되지 않습니다.
- 원본 값이 대상 타입의 두 표현 가능한 값 사이에 있는 경우, 결과는 두 값 중 하나입니다 (어떤 값이 될지는 구현에 따라 정의되며, IEEE 산술이 지원되는 경우 기본적으로 가장 가까운 값으로 반올림 됩니다).
- 그 외의 경우, 동작은 정의되지 않습니다.
부동 소수점-정수 변환
부동소수점 타입의 prvalue 는 임의의 정수 타입 prvalue로 변환될 수 있습니다. 소수 부분은 절사되며, 즉 소수 부분은 버려집니다.
- 잘린 값이 대상 유형에 맞출 수 없는 경우, 동작은 정의되지 않습니다 (대상 유형이 부호 없는 경우에도 모듈러 연산이 적용되지 않습니다).
- 대상 유형이 bool 인 경우, 이것은 부울 변환입니다 ( 아래 참조 ).
정수 또는 비범위 열거형 타입의 prvalue는 모든 부동소수점 타입의 prvalue로 변환될 수 있습니다. 가능한 경우 결과는 정확합니다.
- 값이 대상 타입에 들어맞지만 정확히 표현될 수 없는 경우, 가장 가까운 상위 표현 가능 값과 가장 가까운 하위 표현 가능 값 중 어느 쪽이 선택될지는 구현에 따라 정의되며, IEEE 산술이 지원되는 경우 기본적으로 가장 가까운 값으로 반올림 됩니다.
- 값이 대상 타입에 들어맞지 않는 경우, 동작은 정의되지 않습니다.
- 소스 타입이 bool 인 경우, false 값은 0으로 변환되고, true 값은 1로 변환됩니다.
포인터 변환
null pointer constant 은(는) 모든 포인터 타입으로 변환될 수 있으며, 그 결과는 해당 타입의 null 포인터 값입니다. 이러한 변환(일명 null pointer conversion )은 cv-qualified 타입으로의 단일 변환으로 허용됩니다. 즉, 이는 수치 변환과 한정자 변환의 조합으로 간주되지 않습니다.
어떤 (선택적으로 cv-한정자 적용 가능한) 객체 타입
T
에 대한
prvalue
포인터는 (동일하게 cv-한정된)
void
에 대한 prvalue 포인터로 변환될 수 있습니다. 결과 포인터는 원본 포인터 값과 동일한 메모리 위치를 나타냅니다.
- 원본 포인터가 널 포인터 값인 경우, 결과는 대상 타입의 널 포인터 값입니다.
"pointer to (possibly cv-qualified)
Derived
" 타입의 prvalue
ptr
는 "pointer to (possibly cv-qualified)
Base
" 타입의 prvalue로 변환될 수 있습니다. 여기서
Base
는
Derived
의
base class
이며,
Derived
는
complete
class type입니다.
Base
가 접근 불가능하거나 모호한 경우, 프로그램은 ill-formed입니다.
- 만약 ptr 이 null 포인터 값이면, 결과 또한 null 포인터 값입니다.
-
그렇지 않고,
Base가 가상 기본 클래스 이고Derived의 ptr 이Derived와 유사한 타입의 객체를 가리키지 않으며, 해당 객체가 수명 내에 있거나 생성/소멸 기간 내에 있지 않으면, 동작은 정의되지 않습니다. - 그렇지 않으면, 결과는 파생 클래스 객체의 기본 클래스 하위 객체에 대한 포인터입니다.
멤버-포인터 변환
널 포인터 상수 는 모든 멤버-대-포인터 타입으로 변환될 수 있으며, 그 결과는 해당 타입의 널 멤버 포인터 값입니다. 이러한 변환( 널 멤버 포인터 변환 으로 알려짐)은 단일 변환으로 cv-한정 타입으로의 변환이 허용됩니다. 즉, 이는 수치 변환과 한정 변환의 조합으로 간주되지 않습니다.
"cv-한정된(cv-qualified)
T
타입의
Base
멤버에 대한 포인터" 타입의
prvalue
는 "동일하게 cv-한정된
T
타입의
Derived
멤버에 대한 포인터" 타입의 prvalue로 변환될 수 있습니다. 여기서
Base
는
Derived
의 기본 클래스이며,
Derived
는 완전한 클래스 타입입니다. 만약
Base
가
Derived
의 접근 불가, 모호하거나 가상 기본 클래스이거나,
Derived
의 일부 중간 가상 기본 클래스의 기본 클래스인 경우 프로그램은 형식에 맞지 않습니다.
-
만약
Derived가 원본 멤버를 포함하지 않고, 원본 멤버를 포함하는 클래스의 기본 클래스가 아닌 경우, 동작은 정의되지 않습니다. -
그렇지 않으면, 결과 포인터는
Derived객체로 역참조될 수 있으며, 해당Derived객체의Base기본 하위 객체 내 멤버에 접근하게 됩니다.
불리언 변환
정수형, 부동소수점형, 비한정 열거형, 포인터, 멤버 포인터 타입의 prvalue 는 bool 타입의 prvalue로 변환될 수 있습니다.
값 0(정수형, 부동소수점형, 비범위 열거형의 경우)과 널 포인터 및 널 멤버 포인터 값은 false 가 됩니다. 다른 모든 값들은 true 가 됩니다.
|
직접 초기화 문맥에서, bool 객체는 std::nullptr_t 타입의 prvalue(포함 nullptr )로부터 초기화될 수 있습니다. 결과 값은 false 입니다. 그러나 이것은 암시적 변환으로 간주되지 않습니다. |
(C++11부터) |
한정 변환
일반적으로 말해서:
-
prvalue
타입의 포인터가
cv-qualified
타입
T를 가리킬 때, 동일한 타입T의 더 많은 cv-qualified 포인터 prvalue로 변환될 수 있습니다(즉, constness와 volatility를 추가할 수 있습니다). -
클래스
X내의 cv-qualified 타입T의 멤버 포인터 prvalue는 클래스X내의 더 많은 cv-qualified 타입T의 멤버 포인터 prvalue로 변환될 수 있습니다.
"qualification conversion"의 공식 정의는 아래 에서 확인할 수 있습니다.
유사한 유형
비공식적으로, 두 타입은 최상위 수준의 cv-한정자를 무시했을 때 유사(similar) 합니다:
- 동일한 타입인 경우; 또는
- 둘 다 포인터이고, 가리키는 타입이 유사한 경우; 또는
- 둘 다 동일한 클래스의 멤버 포인터이고, 가리키는 멤버의 타입이 유사한 경우; 또는
- 둘 다 배열이고 배열 요소 타입이 유사한 경우.
예를 들어:
- const int * const * 와 int ** 는 유사합니다;
- int ( * ) ( int * ) 와 int ( * ) ( const int * ) 는 유사하지 않습니다;
- const int ( * ) ( int * ) 와 int ( * ) ( int * ) 는 유사하지 않습니다;
- int ( * ) ( int * const ) 와 int ( * ) ( int * ) 는 유사합니다 (동일한 타입입니다);
- std:: pair < int , int > 와 std:: pair < const int , int > 는 유사하지 않습니다.
공식적으로, 타입 유사성은 자격 분해(qualification-decomposition) 측면에서 정의됩니다.
타입
T
의
qualification-decomposition
은
cv_i
와
P_i
컴포넌트들의 시퀀스로서,
T
가 비음수
n
에 대해 "
cv_0 P_0 cv_1 P_1 ... cv_n−1 P_n−1 cv_n U
" 형태임을 의미합니다. 여기서
-
각
cv_i는 const 와 volatile 의 집합이며, -
각
P_i는
-
- "pointer to",
-
"pointer to member of class
C_iof type", - "array of N_i ", or
- "array of unknown bound of".
만약
P_i
가 배열을 지정하는 경우, 요소 타입의 cv 한정자
cv_i+1
는 배열의 cv 한정자
cv_i
로도 취급됩니다.
// T는 "const int에 대한 포인터에 대한 포인터"이며, 3개의 자격 분해를 가짐: // n = 0 -> cv_0는 비어 있음, U는 "const int에 대한 포인터에 대한 포인터" // n = 1 -> cv_0는 비어 있음, P_0는 "포인터", // cv_1는 비어 있음, U는 "const int에 대한 포인터" // n = 2 -> cv_0는 비어 있음, P_0는 "포인터", // cv_1는 비어 있음, P_1는 "포인터", // cv_2는 "const", U는 "int" using T = const int**; // 다음 타입 중 하나를 U에 대입하면 분해 중 하나를 얻음: // U = U0 -> n = 0인 분해: U0 // U = U1 -> n = 1인 분해: [U1]에 대한 포인터 // U = U2 -> n = 2인 분해: [const U2]에 대한 포인터에 대한 포인터 using U2 = int; using U1 = const U2*; using U0 = U1*;
두 유형
T1
과
T2
가
유사(similar)
하다고 함은 각각에 대한 자격 분해(qualification-decomposition)가 존재하고, 두 자격 분해에 대해 다음 모든 조건이 충족되는 경우를 말합니다:
- 그들은 동일한 n 을 가집니다.
-
U로 표시된 타입들이 동일합니다. -
해당하는
P_i구성 요소들이 모든 i 에 대해 동일하거나 하나는 "배열 of N_i "이고 다른 하나는 "unknown bound of 배열"입니다 (C++20부터) .
// n = 2인 자격 분해: // [const int에 대한 volatile 포인터]에 대한 포인터 using T1 = const int* volatile *; // n = 2인 자격 분해: // [int에 대한 포인터]에 대한 const 포인터 using T2 = int** const; // 위의 두 자격 분해에 대해 // cv_0, cv_1, cv_2가 모두 다르지만, // 동일한 n, U, P_0, P_1을 가지므로 // T1과 T2 타입은 유사합니다.
cv 한정자 조합
아래 설명에서, 타입
Tn
의 가장 긴 qualification-decomposition은
Dn
으로 표기하며, 그 구성 요소들은
cvn_i
와
Pn_i
로 표기합니다.
|
두 타입
|
(C++20 이전) |
|
두 타입
|
(C++20 이후) |
// T1의 가장 긴 한정자 분해 (n = 2): // [char]에 대한 포인터에 대한 포인터 using T1 = char**; // T2의 가장 긴 한정자 분해 (n = 2): // [const char]에 대한 포인터에 대한 포인터 using T2 = const char**; // D3의 cv3_i 및 T_i 구성 요소 결정 (n = 2): // cv3_1 = 비어 있음 (비어 있는 cv1_1과 비어 있는 cv2_1의 합집합) // cv3_2 = "const" (비어 있는 cv1_2와 "const" cv2_2의 합집합) // P3_0 = "pointer to" (알려지지 않은 경계의 배열 없음, P1_0 사용) // P3_1 = "pointer to" (알려지지 않은 경계의 배열 없음, P1_1 사용) // cv_2를 제외한 모든 구성 요소가 동일하며, cv3_2는 cv1_2와 다름, // 따라서 [1, 2) 범위의 각 k에 대해 cv3_k에 "const"를 추가: cv3_1이 "const"가 됨. // T3는 "const char에 대한 const 포인터에 대한 포인터", 즉 const char* const *. using T3 = /* T1과 T2의 한정자 결합 타입 */; int main() { const char c = 'c'; char* pc; T1 ppc = &pc; T2 pcc = ppc; // 오류: T3는 cv-unqualified T2와 동일하지 않음, // 암시적 변환 없음. *pcc = &c; *pc = 'C'; // 위의 잘못된 할당이 허용되면, // const 객체 "c"가 수정될 수 있음. }
C 프로그래밍 언어에서는, const / volatile 이 첫 번째 수준에만 추가될 수 있습니다:
char** p = 0; char * const* p1 = p; // C와 C++ 모두에서 유효함 const char* const * p2 = p; // C에서는 오류, C++에서는 유효함
함수 포인터 변환
void (*p)(); void (**pp)() noexcept = &p; // error: cannot convert to pointer to noexcept function struct S { typedef void (*p)(); operator p(); }; void (*q)() noexcept = S(); // error: cannot convert to pointer to noexcept function |
(C++17부터) |
안전한 bool 문제
C++11 이전까지, 불리언 문맥(예: if ( obj ) { ... } )에서 사용 가능해야 하는 클래스를 설계하는 것은 문제를 제기했습니다: 사용자 정의 변환 함수(예: T :: operator bool ( ) const ; )가 주어졌을 때, 암시적 변환 시퀀스는 해당 함수 호출 이후에 하나의 추가 표준 변환 시퀀스를 허용했습니다. 이는 결과적인 bool 이 int 으로 변환될 수 있음을 의미하며, obj << 1 ; 또는 int i = obj ; 와 같은 코드를 허용했습니다.
이에 대한 초기 해결책은 std::basic_ios 에서 볼 수 있는데, 여기서는 처음에 operator void * 를 정의하여 if ( std:: cin ) { ... } 와 같은 코드가 void * 가 bool 로 변환 가능하기 때문에 컴파일되지만, int n = std:: cout ; 는 void * 가 int 로 변환되지 않기 때문에 컴파일되지 않습니다. 그러나 이 방식은 여전히 delete std:: cout ; 와 같은 무의미한 코드가 컴파일되는 것을 허용합니다.
C++11 이전의 많은 서드파티 라이브러리는 더 정교한 해결책인 Safe Bool idiom 으로 설계되었습니다. std::basic_ios 또한 LWG issue 468 를 통해 이 관용구를 허용했으며, operator void * 는 대체되었습니다( 참고 참조).
C++11부터, explicit bool 변환 을 사용하여 safe bool 문제를 해결할 수도 있습니다.
결함 보고서
다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.
| DR | 적용 대상 | 게시된 동작 | 올바른 동작 |
|---|---|---|---|
| CWG 170 | C++98 |
파생 클래스가 원래 멤버를 가지고 있지 않은 경우
멤버 포인터 변환의 동작이 불명확했음 |
명확하게 규정됨 |
| CWG 172 | C++98 | 열거형 타입이 기반 타입에 따라 승격되었음 | 값 범위에 따라 승격되도록 변경됨 |
|
CWG 330
( N4261 ) |
C++98 | double * const ( * p ) [ 3 ] 에서 double const * const ( * p ) [ 3 ] 로의 변환이 유효하지 않았음 | 유효하게 변경됨 |
| CWG 519 | C++98 |
null pointer 값이 다른 포인터 타입으로 변환될 때
보존된다는 보장이 없었음 |
항상 보존됨 |
| CWG 616 | C++98 |
초기화되지 않은 객체와 유효하지 않은 값을 가진
포인터 객체의 lvalue에서 rvalue 변환 동작은 항상 정의되지 않음 |
indeterminate
unsigned
char
는 허용됨; 유효하지 않은 포인터 사용은 구현에 따라 정의됨 |
| CWG 685 | C++98 |
열거형 타입의 기반 타입이 고정된 경우
정수 승격에서 우선순위가 부여되지 않았음 |
우선순위 부여 |
| CWG 707 | C++98 |
정수에서 부동 소수점 변환은
모든 경우에 정의된 동작을 가짐 |
변환되는 값이 대상 범위를
벗어나는 경우 동작이 정의되지 않음 |
| CWG 1423 | C++11 | std::nullptr_t 가 직접 초기화와 복사 초기화 모두에서 bool 로 변환 가능했음 | 직접 초기화만 가능 |
| CWG 1773 | C++11 |
잠재적으로 평가되는 표현식에 나타나는 이름 표현식은
해당 객체가 odr-used되지 않더라도 lvalue-to-rvalue 변환 과정에서 여전히 평가될 수 있음 |
평가되지 않음 |
| CWG 1781 | C++11 |
std::nullptr_t
에서
bool
로의 변환이 직접 초기화에서만 유효함에도 불구하고
암시적 변환으로 간주되었음 |
더 이상 암시적 변환으로
간주되지 않음 |
| CWG 1787 | C++98 |
레지스터에 캐시된 불확정
unsigned char 값을 읽는 동작이 정의되지 않았음 |
명확하게 정의됨 |
| CWG 1981 | C++11 | 컨텍스트 변환이 명시적 변환 함수로 간주됨 | 고려되지 않음 |
| CWG 2140 | C++11 |
lvalue-to-rvalue 변환이
std::nullptr_t lvalue로부터 이러한 lvalue를 메모리에서 가져오는지 명확하지 않았음 |
가져오지 않음 |
| CWG 2310 | C++98 |
파생 클래스에서 기본 클래스로의 포인터 변환과
기본 클래스에서 파생 클래스로의 멤버 포인터 변환의 경우, 파생 클래스 타입이 불완전할 수 있었음 |
완전해야 함 |
| CWG 2484 | C++20 |
char8_t
와
char16_t
가 서로 다른 정수 승전
전략을 가졌으나, 둘 다 수용 가능함 |
char8_t
는
char16_t
와 동일한 방식으로
승전되어야 함 |
| CWG 2485 | C++98 | 비트 필드를 포함하는 정수 승격이 명확하게 명시되지 않았음 | 명세를 개선함 |
| CWG 2813 | C++23 |
클래스 prvalue의 명시적 객체 멤버 함수가 호출될 때
임시 구체화가 발생함 |
이 경우에는
발생하지 않음 |
| CWG 2861 | C++98 |
타입 접근 불가능 객체에 대한 포인터가
기본 클래스 서브객체에 대한 포인터로 변환될 수 있었음 |
이 경우 동작은
정의되지 않음 |
| CWG 2879 | C++17 |
prvalue 피연산자에 임시 구체화 변환이 적용됨
glvalue를 기대하는 연산자의 피연산자로 사용될 때 |
일부 경우에 적용되지 않음 |
| CWG 2899 | C++98 |
lvalue-to-rvalue 변환을 유효하지 않은 값 표현을 가진 객체를 지정하는 lvalue에 적용할 수 있었음
|
이 경우 동작은
정의되지 않음 |
| CWG 2901 | C++98 | unsigned int lvalue에서 int 객체를 참조하며 값이 - 1 인 경우의 lvalue-to-rvalue 변환 결과가 불명확했음 | 명확하게 규정됨 |
참고 항목
|
C documentation
for
Implicit conversions
|