[[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тому, що типи e1та e2однакові.

Однак мені дуже хочеться мати Zрозміри 1. Це змусило мене замислитись, що робити із загортанням Emptyу якийсь клас обгортки із додатковим параметром шаблону, який застосовує різні типи e1та e2.

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;
};

Що є порожнім типом, якщо Tі Sтакож є порожніми типами та різними! Я хочу , щоб цей тип , щоб бути порожнім , навіть якщо Tі Sті ж типи.


2
А як щодо додавання аргументів шаблону до Tсебе? Це створило б різні типи. Зараз факт, що обидва Wrapperспадкують від Tвас, стримує вас ...
Макс Ленгоф

@MaxLanghof Що ви маєте на увазі, додавши аргумент шаблону T? Зараз Tаргумент шаблону.
Том

Не успадкуйте від T.
Evg

@Evg тут не має значення.
eerorika

2
Просто тому , що це більше , ніж 1 цієї статті не робить його непустою: coliru.stacked-crooked.com/a/51aa2be4aff4842e
Deduplicator

Відповіді:


6

Що є порожнім типом, якщо Tі Sтакож є порожніми типами та різними! Я хочу , щоб цей тип , щоб бути порожнім , навіть якщо Tі Sті ж типи.

Ви не можете цього отримати. Технічно кажучи, ви навіть не можете гарантувати , що це буде порожній , навіть якщо Tі Sрізні порожні типи. Пам'ятайте: no_unique_addressатрибут; здатність приховувати об'єкти повністю залежить від реалізації. З точки зору стандартів, ви не можете застосувати розмір порожніх об'єктів.

По мірі дозрівання C ++ 20, ви повинні припустити, що [[no_unique_address]]зазвичай дотримуватимуться правил оптимізації порожньої бази. А саме, поки два об'єкти одного типу не є суб'єктами, ви, ймовірно, можете розраховувати на приховування. Але в цей момент це своєрідна удача з горщиком.

Що стосується конкретного випадку Tта Sтого ж типу, це просто неможливо. Незважаючи на наслідки назви "no_unique_address", реальність полягає в тому, що C ++ вимагає, щоб, задавши два покажчики на об'єкти одного типу, ці вказівники або вказували на один і той же об'єкт, або мають різні адреси. Я називаю це "унікальним правилом ідентичності", і no_unique_addressце не впливає. Від [intro.object] / 9 :

Два об’єкти з життями, що перекриваються, що не є бітовими полями, можуть мати однакову адресу, якщо один вкладений в інший, або якщо принаймні один є суб'єктом нульового розміру і вони мають різні типи ; в іншому випадку вони мають окремі адреси і займають непересічні байти зберігання.

Члени порожніх типів, оголошені [[no_unique_address]]нульовим розміром, але наявність одного типу робить це неможливим.

Дійсно, думка про це, спроба приховати порожній тип за допомогою гніздування, все ще порушує унікальне правило ідентичності. Розглянемо свій Wrapperі Z1випадок. З огляду на, z1що є екземпляром Z1, зрозуміло, що це z1.e1і z1.e2різні об'єкти різного типу. Однак z1.e1не вкладається z1.e2ні в ні, ні навпаки. І хоча вони мають різні типи, (Empty&)z1.e1і не(Empty&)z1.e2 є різними типами. Але вони вказують на різні об'єкти.

І за унікальним правилом ідентичності вони повинні мати різні адреси. Тож, хоча вони e1і e2номінально різні типи, їх внутрішні органи також повинні підкорятися унікальній ідентичності щодо інших суб'єктів у тому ж об'єкті, що міститься. Рекурсивно.

Те, що ви хочете, просто неможливо в C ++, як це зараз є, незалежно від того, як ви намагаєтеся.


Чудове пояснення, велике спасибі!
Том

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>{})==2де A - абсолютно порожня структура.

Ви можете ввести більше спеціалізацій для підтримки рекурсивного стиснення порожніх пар:

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>{})==2де Aє абсолютно порожня структура.
Том

Я додав би get_empty<T>функцію. Потім ви можете повторно використовувати get_empty<T>ліворуч або праворуч, якщо це вже працює.
Якк - Адам Невраумон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.