0

TL;DR:

  1. What is the size of a pointer to an empty class empty_t?
  2. Does the C++20 attribute [[no_unique_address]] have any impact on that pointer size?

Particularly, the first seems to be quite an old question, but it's hard to find information on the Internet (because the search is spoiled by more trivial information on pointers and empty base-class optimization).


Longer story:

I have written a custom container that conditionally contains some parameters some_parameters_t. In case those are absent, I use [[no_unique_address]] in order to not increase the size of the object.

This works fine for the plain parameter type. Now, I set up an iterator containing a pointer to this parameters. Is this pointer guaranteed to be empty as well, when the pointee is?

Here is a synthetic example code:

struct some_parameters_t
{
    int important_parameter = 42;
};

struct empty_t {};

template<bool use_parameters>
struct my_container
{
    using param_t = std::conditional_t<use_parameters, some_parameters_t, empty_t>;
    [[no_unique_address]] param_t param_;
    vector<double> vec_;

    struct iterator
    {
        iterator(vector<double>* vec, param_t* p) : vec_(vec), p_(p) {}           

        vector<double>* vec_;

        [[no_unique_address]] param_t* p_;   //is this guaranteed to have zero size when being empty_t* ?

        using param_ptr = std::conditional_t<use_parameters, some_parameters_t*, empty_t>;
        [[no_unique_address]] param_ptr p_;  //or must I use this conditional_t construct once again to get size 0?                        
    };
    
    //...
};

The question is: When [[no_unique_address]] param_t* conditionally becomes [[no_unique_address]] empty_t*, is it guaranteed to be of zero size?

Or, do I have to use the std::conditional_t<use_parameters, some_parameters_t*, empty_t> trick again, together with [[no_unique_address]] param_ptr p_ in order to arrive at size 0?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
davidhigh
  • 14,652
  • 2
  • 44
  • 75
  • 1
    I highly doubt there is _any_ difference between pointers to empty/non empty classes. You have tons of different objects of any type, after all, with different addresses. `empty_t a, b;` — I expect that `&a != &b` is guaranteed (but not entirely sure). – yeputons Jan 28 '23 at 22:08
  • 1
    Unrelated note: `::empty_t` and `::some_parameters_t` are [names reserved by POSIX](https://stackoverflow.com/a/228797/767632). – yeputons Jan 28 '23 at 22:09

1 Answers1

2

What is the size of a pointer to an empty class empty_t?

The size of the pointer. On modern platforms, all pointers have the same size.

Does the C++20 attribute [[no_unique_address]] does have any impact on that pointer size?

That is not specified.

When [[no_unique_address]] param_t* conditionally becomes [[no_unique_address]] empty_t*, is guaranteed to be of zero size?

No.

do I have to use the std::conditional_t<use_parameters, some_parameters_t*, empty_t> trick again, together with [[no_unique_address]] param_ptr p_ in order to arrive at size 0?

Yes.

It's really easy to check, just std::cout << sizeof(my_container<0>::iterator); and add/remove the member and see if it changes. Etc.

KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • 1
    Thanks for your answer, it adresses all asked points. Why not `my_container& parent`? Because the corresponding iterator would not be default-constructible/copyable as an it should, and because it would induce a higher coupling as necessary. – davidhigh Jan 28 '23 at 22:21
  • Hm..... sooo `my_container*` :D – KamilCuk Jan 28 '23 at 22:23
  • Higher coupling than necessary :-) – davidhigh Jan 28 '23 at 22:25
  • Are there any references for these statements? Because I was assuming the same beforehnd (--pointer has the usual size and [[no_unique_address]] has no impact). Is this a gap in [[no_unique_address]]? I mean, if the class itself has size 0, there's no need to store a pointer to it. What do you think? – davidhigh Jan 28 '23 at 23:43
  • @davidhigh no, that just makes pointers needlessly complicated. If you want to not save a pointer, explicitly do so by specializing with `std::is_empty`. – Passer By Jan 29 '23 at 01:23
  • No unique address doesn't mean no address. But also think of it this way: `empty_t*` is a type. It has to have _some_ fixed size. It can't be "0 when the `empty_t` has the `[[no_unique_address]]` attached to it and however big a pointer normally is the rest of the time". – Nathan Pierson Jan 29 '23 at 01:26
  • Practically, `no_unique_address` means that the pointer of that member _points_ to the next member in the class (or to the end of the class). So in this case `offsetof(my_container, param_) == offsetof(my_container, vec_)` like in pseudocode `&my_container::param_ == &my_container::vec_`. Because of that, the _pointer_ itself to `param_` still has size, because it just points to `vec_`, has the same value as if it would point to `vec_`. – KamilCuk Jan 29 '23 at 10:54