[[no_unique_address]]和两个相同类型的成员值


16

我玩弄[[no_unique_address]]c++20

cppreference的示例中,我们有一个空类型EmptyZ

struct Empty {}; // empty class

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

显然,的大小Z至少必须是2因为e1和的类型e2相同。

但是,我真的很想拥有Zsize 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.1clang version 9.0.0


在我的应用程序中,我有很多空类型的表格

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

如果T并且S是空类型且与众不同,则为空类型!我希望此类型为空,即使TS类型相同。


2
如何向T自身添加模板参数?那会产生不同的类型。现在,两个人都Wrapper继承的事实使T您退缩……
Max Langhof

@MaxLanghof通过向其添加模板参数是什么意思T?现在,T是一个模板参数。
汤姆

不要继承T
EVG

@Evg在这里没有区别。
eerorika '19

2
仅仅因为它大于1并不意味着它不是非空的:coliru.stacked-crooked.com/a/51aa2be4aff4842e
Deduplicator

Answers:


6

如果T并且S是空类型且与众不同,则为空类型!我希望此类型为空,即使TS类型相同。

你不明白这一点。从技术上讲,即使TS是不同的空类型,您甚至不能保证它将为空。记住:no_unique_address是一个属性;它隐藏对象的能力完全是取决于实现。从标准的角度来看,您不能强制执行空对象的大小。

随着C ++ 20实现的成熟,您应该假定[[no_unique_address]]通常遵循空基础优化的规则。即,只要两个相同类型的对象不是子对象,就可能希望隐藏起来。但是在这一点上,它有点运气。

至于特定情况TS是相同的类型,即根本不可能的。尽管有名称“ no_unique_address”的含义,但现实是C ++要求给定两个指向相同类型对象的指针,这些指针要么指向同一对象,要么具有不同的地址。我称此为“唯一身份规则”,no_unique_address但不影响该规则。来自[intro.object] / 9

如果一个对象嵌套在另一个对象中,或者如果至少一个对象是零大小的子对象并且它们是不同类型的,则两个生命周期重叠且不是位域的对象可能具有相同的地址;否则,它们具有不同的地址并占用不相交的存储字节。

声明为空类型的成员的[[no_unique_address]]大小为零,但具有相同类型的成员使此操作不可能实现。

确实,考虑到这一点,尝试通过嵌套隐藏空类型仍然违反了唯一身份规则。考虑你WrapperZ1情况。给定a z1是作为的实例Z1,显然z1.e1z1.e2是具有不同类型的不同对象。但是,z1.e1它不会嵌套在内部z1.e2,反之亦然。虽然他们有不同的类型,(Empty&)z1.e1并且(Empty&)z1.e2没有不同的类型。但是它们确实指向不同的对象。

并且根据唯一身份规则,它们必须具有不同的地址。因此,即使e1e2名义上是不同的类型,其内部还必须针对同一对象包含其他子对象服从独特的身份。递归地。

无论您如何尝试,在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是一个完全空的结构。
汤姆(Tom),

我要添加一个get_empty<T>功能。然后,您可以重新使用get_empty<T>左侧或右侧(如果已在此处使用)。
Yakk-亚当·内夫罗蒙特
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.