18

我在[[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用额外的模板参数包装一些包装类怎么样,这些模板参数强制不同类型的e1and 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. 有没有一个技巧可以使 sizeZ1成为一个?

我正在测试这个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;
};

ifT并且S也是空类型并且不同,这是一个空类型!我希望这种类型为空,即使TS是相同的类型。

4

2 回答 2

7

ifT并且S也是空类型并且不同,这是一个空类型!我希望这种类型为空,即使TS是相同的类型。

你不能得到那个。T从技术上讲,即使和S是不同的空类型,您甚至不能保证它是空的。记住:no_unique_address是一个属性;它隐藏对象的能力完全取决于实现。从标准的角度来看,您不能强制规定空对象的大小。

随着 C++20 实现的成熟,您应该假设它[[no_unique_address]]通常会遵循空基优化的规则。也就是说,只要两个相同类型的对象不是子对象,您就可以期望隐藏。但在这一点上,这是一种运气。

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

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

声明为的空类型的成员[[no_unique_address]]大小为零,但具有相同类型使得这不可能。

事实上,仔细想想,试图通过嵌套来隐藏空类型仍然违反了唯一标识规则。考虑你的Wrapper情况Z1。给定 a z1which 是 的一个实例Z1,很明显z1.e1z1.e2是具有不同类型的不同对象。但是,z1.e1不嵌套在z1.e2其中,反之亦然。虽然它们有不同的类型,(Empty&)z1.e1并且(Empty&)z1.e2不是不同的类型。但它们确实指向不同的对象。

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

无论您如何尝试,您想要的在 C++ 中都是不可能的。

于 2019-11-29T18:18:12.693 回答
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; };
};
于 2019-11-29T18:01:00.247 回答