GCC9는 std :: variant의 값없는 상태를 피할 수 있습니까?


14

최근에 Reddit 토론을 따라 std::visit컴파일러 에서 최적화를 훌륭하게 비교했습니다 . 나는 다음을 발견했다 : https://godbolt.org/z/D2Q5ED

GCC9와 Clang9 (모두 동일한 stdlib를 공유한다고 생각합니다)는 모든 유형이 특정 조건을 충족 할 때 가치없는 예외를 확인하고 던지는 코드를 생성하지 않습니다. 이것은 더 나은 codegen으로 이어 지므로 MSVC STL에 문제가 발생 했으며이 코드가 표시되었습니다.

template <class T>
struct valueless_hack {
  struct tag {};
  operator T() const { throw tag{}; }
};

template<class First, class... Rest>
void make_valueless(std::variant<First, Rest...>& v) {
  try { v.emplace<0>(valueless_hack<First>()); }
  catch(typename valueless_hack<First>::tag const&) {}
}

주장은 이것이 변형을 무가치하게 만들고 문서를 읽는 것이 다음과 같아야한다는 것입니다.

먼저 현재 포함 된 값 (있는 경우)을 삭제합니다. 그런 다음 T_I인수를 사용하여 유형의 값을 구성하는 것처럼 포함 된 값을 직접 초기화합니다 std::forward<Args>(args)..... 예외가 발생하면 *thisvalueless_by_exception이 될 수 있습니다.

내가 이해하지 못하는 것 : 왜 "may"라고 표시되어 있습니까? 전체 작업이 실패하면 이전 상태를 유지하는 것이 합법입니까? 이것이 GCC가하는 일이기 때문에 :

  // For suitably-small, trivially copyable types we can create temporaries
  // on the stack and then memcpy them into place.
  template<typename _Tp>
    struct _Never_valueless_alt
    : __and_<bool_constant<sizeof(_Tp) <= 256>, is_trivially_copyable<_Tp>>
    { };

그리고 나중에 (조건부) 다음과 같은 작업을 수행합니다.

T tmp  = forward(args...);
reset();
construct(tmp);
// Or
variant tmp(inplace_index<I>, forward(args...));
*this = move(tmp);

따라서 기본적으로 임시를 생성하고 복사가 성공하면 실제 위치로 복사 / 이동합니다.

IMO는 문서에 명시된대로 "먼저 포함 된 값을 삭제합니다"를 위반합니다. 표준을 읽으면 v.emplace(...)변형의 현재 값이 항상 삭제되고 새 유형이 설정 유형이거나 값이없는 것입니다.

조건 is_trivially_copyable에 관찰 가능한 소멸자가있는 모든 유형이 제외됩니다. 따라서 "as-if variant가 이전 값으로 다시 초기화 된 경우"와 같이 될 수도 있습니다. 그러나 변형의 상태는 관찰 가능한 효과입니다. 그렇다면 표준이 실제로 허용 emplace합니까? 현재 값을 변경하지 않습니까?

표준 인용문에 대한 응답으로 편집하십시오.

그런 다음 포함 된 값을 인수가 아닌 TI 유형의 값을 직접 초기화하지 않는 것처럼 초기화합니다 std​::​forward<Args>(args)....

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);실제로 위의 유효한 구현으로 간주 됩니까 ? 이것이 "있는 것처럼"의 의미입니까?

답변:


7

표준의 중요한 부분은 다음과 같습니다.

에서 https://timsong-cpp.github.io/cppwp/n4659/variant.mod#12

23.7.3.4 모디파이어

(...)

템플릿 variant_alternative_t> & emplace (Args && ... args);

(...) 포함 된 값을 초기화하는 동안 예외가 발생하면 변형이 값을 보유하지 않을 수 있습니다

"필수"가 아니라 "필수"라고 말합니다. gcc에서 사용하는 것과 같은 구현을 허용하기 위해 이것이 의도적 인 것으로 기대합니다.

당신이 언급했듯이, 이것은 모든 대안의 소멸자가 사소하고 이전 값을 파괴해야하기 때문에 관찰 할 수없는 경우에만 가능합니다.

후속 질문 :

Then initializes the contained value as if direct-non-list-initializing a value of type TI with the arguments std​::​forward<Args>(args)....

T tmp {std :: forward (args) ...}; this-> value = std :: move (tmp); 실제로 위의 유효한 구현으로 간주됩니까? 이것이 "있는 것처럼"의 의미입니까?

예. 사소하게 복사 가능한 유형의 경우 차이를 감지 할 수있는 방법이 없으므로 구현은 값이 설명 된대로 초기화 된 것처럼 작동합니다. 유형을 간단하게 복사 할 수없는 경우에는 작동하지 않습니다.


흥미 롭군 후속 / 설명 요청으로 질문을 업데이트했습니다. 루트는 : 복사 / 이동이 허용됩니까? might/may표준에 대안이 무엇인지 명시하지 않기 때문에 나는 문구에 매우 혼란스러워합니다 .
Flamefire

이것을 표준 인용으로 받아들이고 there is no way to detect the difference.
Flamefire

5

그렇다면 표준이 실제로 허용 emplace합니까? 현재 값을 변경하지 않습니까?

예. emplace누출이없는 것의 기본 보증을 제공해야한다 (즉, 시공 및 파괴가 관찰 가능한 부작용을 야기 할 때 물체 수명에 대한 존중). 가능한 경우 강한 보장을 제공 할 수있다 (즉, 작동이 실패 할 때 원래 상태가 유지됨).

variant대안은 적절하게 할당 된 스토리지의 한 영역에 할당됩니다. 동적 메모리를 할당 할 수 없습니다. 따라서 유형 변경 emplace은 추가 이동 생성자를 호출하지 않고 원본 객체를 유지할 수있는 방법이 없습니다. 객체를 파괴하고 대신 새 객체를 구성해야합니다. 이 구성이 실패하면 변형은 예외적 인 무가치 상태가되어야합니다. 이것은 존재하지 않는 객체를 파괴하는 것과 같은 이상한 것을 방지합니다.

그러나 작은 복사 가능 유형의 경우 너무 많은 오버 헤드 (이 경우 점검을 피하기위한 성능 향상)없이 강력한 보장을 제공 할 수 있습니다. 따라서 구현이 수행합니다. 이것은 표준을 준수합니다. 구현은 표준에서 요구하는대로보다 사용자 친화적 인 방식으로 기본 보증을 제공합니다.

표준 인용문에 대한 응답으로 편집하십시오.

그런 다음 포함 된 값을 인수가 아닌 TI 유형의 값을 직접 초기화하지 않는 것처럼 초기화합니다 std​::​forward<Args>(args)....

T tmp {std​::​forward<Args>(args)...}; this->value = std::move(tmp);실제로 위의 유효한 구현으로 간주 됩니까 ? 이것이 "있는 것처럼"의 의미입니까?

예, 이동 할당으로 관찰 가능한 효과가 없으면 사소하게 복사 가능한 유형의 경우입니다.


나는 논리 추론에 전적으로 동의합니다. 이것이 실제로 표준인지 확실하지 않습니다. 이것으로 무엇이든 백업 할 수 있습니까?
Flamefire

@Flamefire Hmm ... 일반적으로 표준 기능은 기본 보증을 제공하며 (사용자가 제공 한 내용에 문제가있는 경우 제외) std::variant이를 깨뜨릴 이유가 없습니다. 나는 이것이 표준의 말로 더 명확하게 표현 될 수 있다는 데 동의하지만 이것은 기본적으로 표준 라이브러리의 다른 부분이 작동하는 방식입니다. 참고로 P0088 은 최초 제안이었습니다.
LF

감사. 좀 더 명시 적 사양 내부가 : if an exception is thrown during the call toT’s constructor, valid()will be false;이 "최적화"를 금지했다 그래서
Flamefire

예. emplaceP0088의 규격Exception safety
Flamefire

@Flamefire는 원래 제안서와 투표 한 버전간에 차이가있는 것으로 보입니다. 최종 버전은 "may"문구로 변경되었습니다.
LF
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.