[[no_unique_address]] 및 동일한 유형의 두 멤버 값


16

[[no_unique_address]]에서 놀고 있습니다 c++20.

cppreference 의 예에서는 빈 유형 Empty과 유형이 있습니다.Z

struct Empty {}; // empty class

struct Z {
    char c;
    [[no_unique_address]] Empty e1, e2;
};

분명히,의 크기는 Z최소한이어야 2하기 때문에 종류의 e1e2동일합니다.

그러나, 나는 정말로 Zsize 를 갖고 싶다 1. 이것은 나에게 무엇을 포장에 대한 생각 가지고 Empty서로 다른 유형의 시행 별도의 템플릿 매개 변수 일부 래퍼 클래스 e1e2.

template <typename T, int i>
struct Wrapper : public T{};

struct Z1 {
    char c;
    [[no_unique_address]] Wrapper<Empty,1> e1;
    [[no_unique_address]] Wrapper<Empty,2> e2;
};

불행히도 sizeof(Z1)==2. 크기를 Z1하나로 만드는 트릭이 있습니까?

나는 이것을 테스트 gcc version 9.2.1하고있다.clang version 9.0.0


내 응용 프로그램에는 빈 양식이 많이 있습니다.

template <typename T, typename S>
struct Empty{
    [[no_unique_address]] T t;
    [[no_unique_address]] S s;
};

어떤 경우는, 하늘의 유형 TS도 빈 유형과 구별된다! 나는이 유형의 경우에도 빈되고 싶어 TS같은 종류가 있습니다.


2
템플릿 인수를 T자체 에 추가하는 것은 어떻습니까? 고유 한 유형을 생성합니다. 지금 두 사람 Wrapper의 상속 사실 T은 당신을 뒤로 물러서 고 있습니다.
Max Langhof

@MaxLanghof 템플릿 인수를 추가하여 무엇을 의미 T합니까? 지금 T은 템플릿 인수입니다.
tom

에서 상속받지 마십시오 T.
Evg.

@Evg는 여기서 차이가 없습니다.
eerorika

2
이 1보다 큰해서가 비어하지 않습니다 coliru.stacked-crooked.com/a/51aa2be4aff4842e
Deduplicator

답변:


6

어떤 경우는, 하늘의 유형 TS도 빈 유형과 구별된다! 나는이 유형의 경우에도 빈되고 싶어 TS같은 종류가 있습니다.

당신은 그것을 얻을 수 없습니다. 기술적으로 당신도이 경우에도 비어있을 것이라는 점을 보장 할 수 없습니다, 말하기 TS다른 빈 유형입니다. 기억하십시오 : no_unique_address속성입니다; 객체를 숨길 수있는 기능은 전적으로 구현에 따라 다릅니다. 표준 관점에서는 빈 객체의 크기를 적용 할 수 없습니다.

C ++ 20 구현이 성숙함에 [[no_unique_address]]따라 일반적으로 빈 기본 최적화 규칙을 따르는 것으로 가정해야 합니다. 즉, 같은 유형의 두 객체가 하위 객체가 아닌 한 숨어있을 것으로 예상 할 수 있습니다. 그러나이 시점에서 그것은 일종의 불운입니다.

동일한 유형 의 특정 사례 T와 관련 S하여 단순히 불가능합니다. "no_unique_address"라는 이름의 의미에도 불구하고 실제로 C ++에서는 동일한 유형의 객체에 대한 두 개의 포인터가 주어지면 해당 포인터가 동일한 객체를 가리 키거나 다른 주소를 갖도록 요구합니다. 나는 이것을 "고유의 정체성 규칙"이라고 부르며, no_unique_address그 영향을 미치지 않습니다. 가입일 [intro.object] / 9 :

비트 필드가 아닌 수명이 겹치는 두 객체는 ​​하나가 다른 하나에 중첩되거나 하나 이상의 크기가 0이고 하위 유형 인 경우 동일한 주소를 가질 수 있습니다 . 그렇지 않으면 별개의 주소가 있고 분리 된 바이트의 저장 영역을 차지합니다.

빈 형식의 멤버는 [[no_unique_address]]크기가 0으로 선언 되었지만 형식이 같으면 불가능합니다.

실제로 그것에 대해 생각하고 중첩을 통해 빈 유형을 숨기려고 시도해도 여전히 고유 한 ID 규칙을 위반합니다. 당신 WrapperZ1사건을 고려하십시오 . 특정 z1의 인스턴스 인을 Z1, 분명하다 z1.e1z1.e2다른 유형의 다른 개체가 있습니다. 그러나, 그 z1.e1안에 z1.e2또는 그 반대로 중첩되어 있지 않습니다. 그리고 그들은 다른 유형을 가지고있는 동안 (Empty&)z1.e1하고 (Empty&)z1.e2있습니다 하지 다른 유형. 그러나 그들은 다른 대상을 지적합니다.

그리고 고유 ID 규칙에 의해, 그들이 있어야 다른 주소를 가지고있다. 그래서 비록 e1e2명목상 다른 종류, 자신의 내부도해야 같은 포함 된 개체를 다른 하위 객체에 대한 순종 고유 ID. 재귀 적으로.

당신이 원하는 것은 C ++에서 시도하는 방법에 관계없이 현재와 같이 불가능합니다.


훌륭한 설명, 감사합니다!
tom

2

내가 알 수있는 한, 두 회원 모두를 원한다면 불가능합니다. 그러나 유형이 동일하고 비어있는 경우 멤버를 하나만 지정할 수 있습니다.

template <typename T, typename S, typename = void>
struct Empty{
    [[no_unique_address]] T t;
    [[no_unique_address]] S s;

    constexpr T& get_t() noexcept { return t; };
    constexpr S& get_s() noexcept { return s; };
};

template<typename TS>
struct Empty<TS, TS, typename std::enable_if_t<std::is_empty_v<TS>>>{
    [[no_unique_address]] TS ts;

    constexpr TS& get_t() noexcept { return ts; };
    constexpr TS& get_s() noexcept { return ts; };
};

물론 멤버가 하나만있는 경우를 처리하기 위해 멤버를 사용하는 나머지 프로그램을 변경해야합니다. 이 경우 어떤 멤버가 사용되는지는 중요하지 않습니다. 결국 고유 주소가없는 상태 비 저장 개체입니다. 표시된 멤버 함수는이를 간단하게 만들어야합니다.

불행히도 sizeof(Empty<Empty<A,A>,A>{})==2A가 완전히 비어있는 구조체입니다.

빈 쌍의 재귀 압축을 지원하기 위해 더 많은 전문화를 도입 할 수 있습니다.

template<class TS>
struct Empty<Empty<TS, TS>, TS, typename std::enable_if_t<std::is_empty_v<TS>>>{
    [[no_unique_address]] Empty<TS, TS> ts;

    constexpr Empty<TS, TS>& get_t() noexcept { return ts; };
    constexpr TS&            get_s() noexcept { return ts.get_s(); };
};

template<class TS>
struct Empty<TS, Empty<TS, TS>, typename std::enable_if_t<std::is_empty_v<TS>>>{
    [[no_unique_address]] Empty<TS, TS> ts;

    constexpr TS&            get_t() noexcept { return ts.get_t(); };
    constexpr Empty<TS, TS>& get_s() noexcept { return ts; };
};

더, 같은 압축합니다 Empty<Empty<A, char>, A>.

template <typename T, typename S>
struct Empty<Empty<T, S>, S, typename std::enable_if_t<std::is_empty_v<S>>>{
     [[no_unique_address]] Empty<T, S> ts;

    constexpr Empty<T, S>& get_t() noexcept { return ts; };
    constexpr S&           get_s() noexcept { return ts.get_s(); };
};

template <typename T, typename S>
struct Empty<Empty<S, T>, S, typename std::enable_if_t<std::is_empty_v<S>>>{
     [[no_unique_address]] Empty<S, T> st;

    constexpr Empty<S, T>& get_t() noexcept { return st; };
    constexpr S&           get_s() noexcept { return st.get_t(); };
};


template <typename T, typename S>
struct Empty<T, Empty<T, S>, typename std::enable_if_t<std::is_empty_v<T>>>{
     [[no_unique_address]] Empty<T, S> ts;

    constexpr T&           get_t() noexcept { return ts.get_t(); };
    constexpr Empty<T, S>  get_s() noexcept { return ts; };
};

template <typename T, typename S>
struct Empty<T, Empty<S, T>, typename std::enable_if_t<std::is_empty_v<T>>>{
     [[no_unique_address]] Empty<S, T> st;

    constexpr T&           get_t() noexcept { return st.get_s(); };
    constexpr Empty<S, T>  get_s() noexcept { return st; };
};

이것은 좋지만 불행히도 완전히 빈 구조체가있는 sizeof(Empty<Empty<A,A>,A>{})==2A입니다.
tom

get_empty<T>함수를 추가하겠습니다 . 그런 다음 get_empty<T>이미 작동하는 경우 왼쪽 또는 오른쪽을 다시 사용할 수 있습니다 .
Yakk-Adam Nevraumont
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.