[c++] nullptr을 uintptr_t로 변환 할 수 있습니까? 다른 컴파일러는 동의하지 않습니다

이 프로그램을 고려하십시오 :

#include <cstdint>
using my_time_t = uintptr_t;

int main() {
    const my_time_t t = my_time_t(nullptr);
}

msvc v19.24로 컴파일하지 못했습니다.

<source>(5): error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'my_time_t'
<source>(5): note: A native nullptr can only be converted to bool or, using reinterpret_cast, to an integral type
<source>(5): error C2789: 't': an object of const-qualified type must be initialized
<source>(5): note: see declaration of 't'

Compiler returned: 2

그러나 clang (9.0.1) 및 gcc (9.2.1)는이 코드를 오류없이 “먹습니다”.

MSVC 동작이 마음에 들지만 표준으로 확인 되었습니까? 즉, clang / gcc의 버그입니까, 아니면 이것이 gcc / clang의 올바른 동작이라는 표준을 해석 할 수 있습니까?



답변

제 생각에 MSVC는 표준 적합성을 따르지 않습니다.

나는이 대답을 C ++ 17 (초안 N4659)을 기반으로하고 있지만 C ++ 14와 C ++ 11은 동등한 단어가 있습니다.

my_time_t(nullptr)접미사 표현식 이며 my_time_t유형이며 (nullptr)괄호로 묶인 이니셜 라이저 목록의 단일 표현식이므로 명시 적 캐스트 표현식과 동일합니다. ( [expr.type.conv] / 2 )

명시 적 캐스트는 특히 몇 가지 다른 C ++ 캐스트 (확장자 포함)를 시도합니다 reinterpret_cast. ( [expr.cast] /4.4 ) 이전 reinterpret_cast에 시도한 캐스트 는 const_castand static_cast(확장자 및 조합으로)이지만 std::nullptr_t일체형으로 캐스트 할 수 없습니다 .

그러나 reinterpret_cast<my_time_t>(nullptr)때문에 성공한다 [expr.reinterpret.cast] / 4 타입의 값이 있다고 std::nullptr_t했을 경우와 같이 일체형으로 변환 할 수 reinterpret_cast<my_time_t>((void*)0)가능하기 때문에 인, my_time_t = std::uintptr_t유형 큰 모든 포인터의 값을 나타내는 정도,이 조건이어야 동일한 표준 단락을 사용하여 void*정수 유형으로 변환 할 수 있습니다 .

함수 표기법이 아닌 캐스트 표기법을 사용하는 경우 MSVC에서 변환을 허용하는 것이 특히 이상합니다.

const my_time_t t = (my_time_t)nullptr;


답변

내가 더 찾을 수 있지만 명시 적 이에 언급 C ++ 표준 작업 초안 (2014)로부터의 변환 std::nullptr_t필수 유형이 금지되어, 이러한 변환이 허용되는지에 대한 언급도 없다!

그러나의 변환의 경우 std::nullptr_t에이 bool 됩니다 명시 적으로 언급 :

4.12 부울 변환
산술, 범위가 지정되지 않은 열거 형, 포인터 또는 멤버 유형에 대한 포인터의 prvalue를 bool 유형의 prvalue로 변환 할 수 있습니다. 0 값, 널 포인터 값 또는 널 멤버 포인터 값은 false로 변환됩니다. 다른 값은 true로 변환됩니다. 직접 초기화 (8.5)의 경우, std :: nullptr_t 유형의 prvalue를 bool 유형의 prvalue로 변환 할 수 있습니다. 결과 값은 false입니다.

또한 이 초안 문서에서 정수 유형으로의 변환 이 언급 된 유일한 위치 std::nullptr_t는 “reinterpret_cast”섹션에 있습니다.

5.2.10 재 해석 캐스트

(4) 포인터는 그것을 잡을 수있을 정도로 큰 일체형으로 명시 적으로 변환 될 수 있습니다. 매핑 함수는 구현 정의되어 있습니다. [참고 : 기본 시스템의 주소 구조를 알고있는 사람들에게는 놀라지 않을 것입니다. — 종료 참고] std :: nullptr_t 유형의 값을 정수 유형으로 변환 할 수 있습니다. 변환은 (void *) 0을 정수 유형으로 변환하는 것과 동일한 의미와 유효성을 갖습니다. [참고 : reinterpret_cast를 사용하여 모든 유형의 값을 std :: nullptr_t 유형으로 변환 할 수 없습니다. — 끝 참고]

따라서이 두 가지 관찰에서 (IMHO) 컴파일러가 올바르다 고 합리적으로 추측 할 수 MSVC있습니다.

편집 : 그러나 “기능 표기법 캐스트”를 사용하면 실제로 반대가 될 수 있습니다! MSVC컴파일러는 예를 들면, C 스타일 캐스트를 사용하여 문제가 없습니다 :

uintptr_t answer = (uintptr_t)(nullptr);

그러나 (코드에서와 같이) 이것에 대해 불평합니다.

uintptr_t answer = uintptr_t(nullptr); // error C2440: '<function-style-cast>': cannot convert from 'nullptr' to 'uintptr_t'

그러나 동일한 표준 초안에서 :

5.2.3 명시 적 형식 변환 (함수 표기법)
(1) 단순 형식 지정자 (7.1.6.2) 또는 형식 이름 지정자 (14.6) 다음에 괄호로 묶은 expression-list는 식 목록이 지정된 경우 지정된 형식의 값을 구성합니다. 표현식 목록이 단일 표현식 인 경우 유형 변환 표현식은 해당 캐스트 표현식 (5.4)과 동일합니다 (정의 된 의미로 정의 된 경우). …

“해당 캐스트 표현식 (5.4)”은 C 스타일 캐스트를 나타낼 수 있습니다.


답변

모두 표준을 준수합니다 (C ++의 경우 n4659 초안 참조).

nullptr [lex.nullptr]에서 다음과 같이 정의됩니다.

포인터 리터럴은 키워드 nullptr입니다. std :: nullptr_t 유형의 prvalue입니다. [참고 : …,이 유형의 prvalue는 널 포인터 상수이며 널 포인터 값 또는 널 멤버 포인터 값으로 변환 될 수 있습니다.]

노트가 표준이 아닌 경우에도 표준의 nullptr경우 널 포인터 값 으로 변환 될 것으로 예상됩니다 .

나중에 [conv.ptr]에서 찾을 수 있습니다 :

널 포인터 상수는 값이 0이거나 std :: nullptr_t 유형의 prvalue를 갖는 정수 리터럴입니다. 널 포인터 상수는 포인터 유형으로 변환 될 수 있습니다. …. 정수 타입의 널 포인터 상수는 std :: nullptr_t 타입의 prvalue로 변환 될 수 있습니다.

여기서 다시 어떤 표준에서 요구하는 것은 즉 0A를 변환 할 수 있습니다 std::nullptr_t그리고 그 nullptr모든 포인터 형식으로 변환 할 수 있습니다.

필자는 표준에 직접 통합 유형 으로 변환 nullptr할 수 있는지 여부에 대한 요구 사항이 없다는 것을 읽었습니다 . 그 시점부터 :

  • MSVC는 엄격한 판독 값을 가지며 변환을 금지합니다
  • Clang과 gcc는 중간 void *전환이 포함 된 것처럼 작동 합니다.

답변