Allow zero size arrays and let them occupy zero bytes.

251 views
Skip to first unread message

Bengt Gustafsson

unread,
Jul 4, 2016, 11:15:15 AM7/4/16
to ISO C++ Standard - Future Proposals
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

2. Define that a zero size array occupies zero bytes.

As zero size arrays have never been allowed in C++ it can't be a breaking change to define that the now allowed zero size arrays actually consume no bytes regardless of the size of the element.

With these two rules in place it is possible to write this:

// Example container class.
template<typename T, typename A = std::allocator<T>> MyContainer {
public:
A& GetAllocator() { *m_allocator; } // Access the only element or if none return some other address (next member) that the caller will not use anyway as it points to 0 bytes of information.

private:
A m_allocator[std::is_empty<A> ? 0 : 1];
};

To be able to wrap the ugly ? operator and give the construct a recognizable name some more provisions are needed, this is as if you wrap the possibly empty array into a class that class is not a zero size array and thus
by the current rules must occupy at least one byte.

3. Let is_empty return true if all members of a class are zero size arrays.

This is needed for rule 4 below and also seems reasonable.

4. Define that an is_empty class containing at least one zero sized array occupies zero bytes when used as a member.

The drawback with this rule except that it complicates the standard somewhat is that it could be tempting to add a `int dummy[0];` to for instance std::allocator to mark it as zero-size while
some coder somewhere may (far fetched, but anyway) rely on the offset of an allocator being different from the offset of adjacent members.

Now a general allow_empty wrapper can be written:

// Use zero sized array if T is empty to get rid of byte consumption.
template<typename T> struct allow_empty {
public:
operator T&() { return *m_data; } // use operator* instead of array indexing to avoid compiler warnings or runtime checks.

private:
T m_data[std::is_empty<T> ? 0 : 1];
};


// Example.
template<typename T, typename A = std::allocator<T>> MyContainer {
public:
A& GetAllocator() { m_allocator; }
private:
std::allow_empty<A> m_allocator; // PROBLEM: While allow_empty is maybe empty, special rule is required for m_allocator to use no space.
};



smili...@googlemail.com

unread,
Jul 4, 2016, 12:26:46 PM7/4/16
to ISO C++ Standard - Future Proposals
On Monday, July 4, 2016 at 5:15:15 PM UTC+2, Bengt Gustafsson wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

One of the (current) SFINAE rules is that attempting to create an array of size zero causes the template instantiation to fail.
This forces the compiler to consider other possible candidates.
Allowing zero sized arrays would cause the instantiation to succeed, which will obviously change the behaviour of existing programs.

  Tobias

 

Nicol Bolas

unread,
Jul 4, 2016, 12:30:24 PM7/4/16
to ISO C++ Standard - Future Proposals


On Monday, July 4, 2016 at 11:15:15 AM UTC-4, Bengt Gustafsson wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

Allow them to do... what?
 
2. Define that a zero size array occupies zero bytes.

The C++ object model requires that objects take up storage. If you have an array that has no objects in it, then you clearly cannot use objects that don't exist. So having an array of 0 objects means that you can't do anything with `a[0]`.

Merely permitting the syntax doesn't work. You have to actually define what that means. Whether it's a zero-sized array or an object that takes no storage or whatever, you still need to do the hard work of deciding what that actually means.

And for a zero-sized array, you also have to decide what it means for the array to decay to a pointer.

How we declare that a variable takes up no storage is not the hard part of having stateless members. Whether it's a magic standard library type, a zero-size array, or a new keyword, that's the easy part. The hard part is deciding what they mean, how pointers to them work, how they get copied, etc.

And you haven't really done any of that work.

FrankHB1989

unread,
Jul 4, 2016, 3:34:06 PM7/4/16
to ISO C++ Standard - Future Proposals


在 2016年7月4日星期一 UTC+8下午11:15:15,Bengt Gustafsson写道:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

As said, SFINAE BOOMs.
 
2. Define that a zero size array occupies zero bytes.

As zero size arrays have never been allowed in C++ it can't be a breaking change to define that the now allowed zero size arrays actually consume no bytes regardless of the size of the element.

With these two rules in place it is possible to write this:

// Example container class.
template<typename T, typename A = std::allocator<T>> MyContainer {
public:
A& GetAllocator() { *m_allocator; } // Access the only element or if none return some other address (next member) that the caller will not use anyway as it points to 0 bytes of information.

private:
A m_allocator[std::is_empty<A> ? 0 : 1];
};

To be able to wrap the ugly ? operator and give the construct a recognizable name some more provisions are needed, this is as if you wrap the possibly empty array into a class that class is not a zero size array and thus
by the current rules must occupy at least one byte.

I don't think it is possible to change the meaning of object types/objects/object model without serious problems and redesigning of the majority part of the language. The only clean (?) way is to propose a new kind of entity in parallel to existed void types, object types, function types and reference types and then you can require it shall always take zero storage.
 

Thiago Macieira

unread,
Jul 4, 2016, 4:12:37 PM7/4/16
to std-pr...@isocpp.org
On segunda-feira, 4 de julho de 2016 08:15:15 PDT Bengt Gustafsson wrote:
> No code would break by just allowing something previously forbidden.

[not specific to the case you're proposed, but in general terms]

Except code that used the fact that it was forbidden to cause a compilation
error.

A.k.a. "poor man's static_assert"

--
Thiago Macieira - thiago (AT) macieira.info - thiago (AT) kde.org
Software Architect - Intel Open Source Technology Center

Bengt Gustafsson

unread,
Jul 6, 2016, 4:59:56 AM7/6/16
to ISO C++ Standard - Future Proposals
I see no problem with defining the meanings you are after here. Use logic. See also below.


Den måndag 4 juli 2016 kl. 18:30:24 UTC+2 skrev Nicol Bolas:


On Monday, July 4, 2016 at 11:15:15 AM UTC-4, Bengt Gustafsson wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

Allow them to do... what?
Be declared. Have their address taken. Said address being being that of the first addressable byte after the previous variable in the scope. From this follows that begin() and end() on zero sized arrays both return this address, so that range based for loops 0 times.
 
 
2. Define that a zero size array occupies zero bytes.

The C++ object model requires that objects take up storage. If you have an array that has no objects in it, then you clearly cannot use objects that don't exist. So having an array of 0 objects means that you can't do anything with `a[0]`.
Of course not, just as with an array sized 1 you can't do anything with a[1], because there is nothing there.

Of course any writing in the standard document mentioning that all C++ objects must take up storage must be amended with a clause that excepts zero sized arrays.
 

Merely permitting the syntax doesn't work. You have to actually define what that means. Whether it's a zero-sized array or an object that takes no storage or whatever, you still need to do the hard work of deciding what that actually means.
I think all that is needed is stated above. 

And for a zero-sized array, you also have to decide what it means for the array to decay to a pointer.
It decays to its address as defined above. At this address there is no valid object, just as there isnt when you do &a[1] for a 1-long array today. I fail to see a problem here. 

How we declare that a variable takes up no storage is not the hard part of having stateless members. Whether it's a magic standard library type, a zero-size array, or a new keyword, that's the easy part. The hard part is deciding what they mean, how pointers to them work, how they get copied, etc.
An advantage of using the zero-sized array idiom is that the question of "how do I iterate over an array of zero sized objects" does not come up as a separate issue. 

And you haven't really done any of that work.
Now I have.

 

Bengt Gustafsson

unread,
Jul 6, 2016, 5:00:22 AM7/6/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com
One of the (current) SFINAE rules is that attempting to create an array of size zero causes the template instantiation to fail.
This forces the compiler to consider other possible candidates.
Allowing zero sized arrays would cause the instantiation to succeed, which will obviously change the behaviour of existing programs.

  Tobias


This is unfortunate. But can you come up with a case where such SFINAE can actually be used? To me it seems that to use this rule you would have to create a zero sized array first and then try to call a function templated on the array size with it. But then you get a hard error
for trying to make the array (under current rules)! Maybe there are more complicated cases but I think there has to be at least one that is possible to use today, or this SFINAE rule is superflous. Given actual uses of this rule an assessment can be made whether breaking such code can be accepted.

Bengt Gustafsson

unread,
Jul 6, 2016, 5:05:11 AM7/6/16
to ISO C++ Standard - Future Proposals
This is the most serious problem among the responses here, I think. Are there known code bases that uses this? Was it tought as a "good way" to do static asserting in the old days?

Not generating error messages for newly created bugs which can then require serious debugging is not good. But if such code bases are under serious development, should they not start using static_assert soon? Can anyone come up with  a way to detect such uses of potentially zero sized arrays (I think that would be hard, unfortunately).

smili...@googlemail.com

unread,
Jul 6, 2016, 5:10:56 AM7/6/16
to ISO C++ Standard - Future Proposals, smili...@googlemail.com

One of the ways the SFINAE-rule is actually used:
  void foo(char (*)[...evaluates to 0 or >0...] =0)
  void foo(...)   // other overloads

which is (currently) equivalent to (e.g.)

  std::disable_if<...evaluates to false/true...>::type foo();
  // other overloads

but would not, if zero-sized arrays are allowed (developers would have to replace "[value]" with "[2*(value)-1]" to get a negative-sized array for "false" to still fail SFINAE).
  
  Tobias
 

Andrey Semashev

unread,
Jul 6, 2016, 7:34:51 AM7/6/16
to std-pr...@isocpp.org
On 07/06/16 11:59, Bengt Gustafsson wrote:
> Den måndag 4 juli 2016 kl. 18:30:24 UTC+2 skrev Nicol Bolas:
> On Monday, July 4, 2016 at 11:15:15 AM UTC-4, Bengt Gustafsson wrote:
>
> 1. Allow zero sized arrays in C++.
>
> No code would break by just allowing something previously
> forbidden. The problem with allowing this could have been to
> make up ones mind about what size such an array should report.
>
>
> Allow them to do... what?
>
> Be declared. Have their address taken. Said address being being that of
> the first addressable byte after the previous variable in the scope.
> From this follows that begin() and end() on zero sized arrays both
> return this address, so that range based for loops 0 times.

It would mean that sizeof would return 0 for that array. This breaks
assumption of that it can never happen in the existing code.

It would also mean that address of the array would be the same as the
address of the following object, assuming the object's alignment
permits. It breaks aliasing rules.

Mathias Gaunard

unread,
Jul 6, 2016, 8:06:37 AM7/6/16
to std-pr...@isocpp.org
On 4 July 2016 at 16:15, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

2. Define that a zero size array occupies zero bytes.

As zero size arrays have never been allowed in C++ it can't be a breaking change to define that the now allowed zero size arrays actually consume no bytes regardless of the size of the element.

So you want

struct Foo
{
   char array[0];
   int value;
};

to be well-formed?

That's problematic for a number of reasons.

Jonathan Coe

unread,
Jul 6, 2016, 9:25:11 AM7/6/16
to std-pr...@isocpp.org
Could you elaborate?

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CALnjya-8Kn7VR3J0yxfPoR%2BzpDGDNxXSUwh4ZpD-t1KdbN9Prw%40mail.gmail.com.

Mathias Gaunard

unread,
Jul 6, 2016, 11:14:58 AM7/6/16
to std-pr...@isocpp.org
On 6 July 2016 at 14:25, Jonathan Coe <jonath...@gmail.com> wrote:

On 6 Jul 2016, at 13:06, Mathias Gaunard <mat...@gaunard.com> wrote:

So you want

struct Foo
{
   char array[0];
   int value;
};

to be well-formed?

That's problematic for a number of reasons.

Could you elaborate?

I'm pretty sure the object model requires members to have different offsets, there is also the issue that you get aliasing of unrelated types etc.
It would be best to discuss this with core experts.

Nicol Bolas

unread,
Jul 6, 2016, 11:34:26 AM7/6/16
to ISO C++ Standard - Future Proposals
On Wednesday, July 6, 2016 at 4:59:56 AM UTC-4, Bengt Gustafsson wrote:
I see no problem with defining the meanings you are after here. Use logic. See also below.

For something as complex as stateless objects, it is you who needs to do the work to come up with all of the corner cases. Or at least a good-faith effort at it. I'm not saying that you need to have complete standards wording at this point, but demonstraiting an understanding of most of the problems in this would go a long way in making others feel confident that you know what you're talking about.

Den måndag 4 juli 2016 kl. 18:30:24 UTC+2 skrev Nicol Bolas:


On Monday, July 4, 2016 at 11:15:15 AM UTC-4, Bengt Gustafsson wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

This idea struck me to solve both of these issues at once by a few small changes:

1. Allow zero sized arrays in C++.

No code would break by just allowing something previously forbidden. The problem with allowing this could have been to make up ones mind about what size such an array should report.

Allow them to do... what?
Be declared. Have their address taken. Said address being being that of the first addressable byte after the previous variable in the scope.

So, given this:

struct Foo
{
 
int f;
 
float g[0];
};

Foo foo;
int j;

What will the address of `foo.g` be? You say that it is "first addressable byte after" `foo.f`. If `g` is not supposed to take up space within `Foo`, then `Foo` should be layout-compatible with a struct with a single `int` member (and if that's not going to be guaranteed, then the whole proposal solves nothing. That is, after all the whole point of stateless subobjects). The size and align of such a struct will likely be the sizeof and alignof an `int`.

Which means that the "first addressable byte" after `foo.f` is... outside of `Foo`. Indeed, given the above, it is entirely possible that the address of `g` is the same address as `j`, a completely unrelated variable.

Which now means that two unrelated objects have the same address. The object rules of C++ do not permit two unrelated objects to live in the same address. So there's another rule that you'll have to change.

Not to mention that the address of a subobject of a type is somewhere outside of that object's storage. It's not clear if that breaks a rule of C++ per-se, since all prior addresses were based on the subobject's storage, and by definition stateless subobjects don't have storage. But even so, it's exceedingly bizarre.

From this follows that begin() and end() on zero sized arrays both return this address, so that range based for loops 0 times.
 
 
2. Define that a zero size array occupies zero bytes.

The C++ object model requires that objects take up storage. If you have an array that has no objects in it, then you clearly cannot use objects that don't exist. So having an array of 0 objects means that you can't do anything with `a[0]`.
Of course not, just as with an array sized 1 you can't do anything with a[1], because there is nothing there.

Of course any writing in the standard document mentioning that all C++ objects must take up storage must be amended with a clause that excepts zero sized arrays.

And what about the fallout from changing that?

How do zero-sized arrays affect the trivial copyability of the type? Can you manually copy a zero-sized subobject?

What is the size of a zero-sized array?

Equally importantly, if you can make the rules allow a zero-sized array, then you have all the groundwork you need (standards wise) to just declare that a particular subobject will be zero size.

So what's the point of using this arcane zero-sized array thing if you can use actual syntax?

Merely permitting the syntax doesn't work. You have to actually define what that means. Whether it's a zero-sized array or an object that takes no storage or whatever, you still need to do the hard work of deciding what that actually means.
I think all that is needed is stated above.

I've looked into this problem before on several occasions. And while I am hardly a master of the standards wording, I can tell you that even after significant research, I still cannot claim to fully understand the ramifications of making such a change, regardless of how you declare such subobjects.

Consider your resolution to the question of what the address of such a variable is. That was a blatant violation of the strict aliasing rule. And the strict aliasing rule is pretty much problem #1 for any stateless subobject proposal (since the subobject will likely have to have a pointer address in its container object, which may be another object type). So anyone who even considers proposing such a thing needs to have at least a cursory understanding of that rule. And yet, you did not demonstrate such an understanding.

So I submit that what you've said above is far from all that needs to be said.

And for a zero-sized array, you also have to decide what it means for the array to decay to a pointer.
It decays to its address as defined above. At this address there is no valid object, just as there isnt when you do &a[1] for a 1-long array today. I fail to see a problem here. 

Wait; if there's no valid object there... what good is making such a declaration?

Is it legal to call member functions for such a non-object? No; such member functions will have a `this` pointer to an invalid object. As such, it is functionally equivalent to calling a member function on a `nullptr` object.

So if you can't actually call member functions of zero-sized objects, what's the point of having them at all?

How we declare that a variable takes up no storage is not the hard part of having stateless members. Whether it's a magic standard library type, a zero-size array, or a new keyword, that's the easy part. The hard part is deciding what they mean, how pointers to them work, how they get copied, etc.
An advantage of using the zero-sized array idiom is that the question of "how do I iterate over an array of zero sized objects" does not come up as a separate issue.

Sure it does. You just haven't thought it through:

struct Bar
{
 
float f[20][0];
};

See? An array of 20 zero-sized objects.

In any case, it's easy enough to simply outright forbid having arrays of stateless objects. But your proposal makes that no easier than any other.

Tony V E

unread,
Jul 7, 2016, 5:52:12 PM7/7/16
to Standard Proposals
Since most compilers already accept zero-sized arrays, I've always used -1 instead of 0.

Matt Calabrese

unread,
Jul 7, 2016, 8:09:37 PM7/7/16
to ISO C++ Standard - Future Proposals
On Mon, Jul 4, 2016 at 8:15 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

When I started working on Regular Void (p0146) I initially explored size 0 types but decided to postpone the effort because it has some difficult practical concerns regarding backwards compatibility. I think it might be possible that we will eventually get to a point where the language allows size 0 types, but it is not something that we can just start introducing (arrays or otherwise) without broader considerations. If the language had size 0 types to begin with then there would be no potential issues, but I half-suspect that the transition to a world where size 0 types exist may be more difficult than people would care to accept.

Here's a brief summary of my current conclusions. I'd like to eventually explore this again once Regular Void either goes into the language or is killed.

1) In order for size 0 types to be useful as a means of minimizing storage, we'd effectively have to change aliasing rules. No two objects of the same type can occupy the same address in C++ as it currently is, which user-level code can and does exploit. We'd want size 0 objects to able to [effectively] share an address. This is true even if you only limit size 0 types to arrays of size 0.
2) The more important concern, IMO, is that size 0 types fundamentally break our assumption that raw pointer types are able to act as iterators into arrays. Again, this problem still exists and needs to be... addressed... (heh) even if you only allow arrays of size 0 and not more general size 0 types.  A simple example of this is as follows:

// This iterates 5 times
int a[5] = {};
for( auto& elem : a )
{
  // ...do whatever...
}

// This iterates 0 times
using foo = int[0];
foo b[5] = {};
for( auto& elem : b )
{
  // ...do whatever...
}

The above might seem contrived, but it's not at all. Once you allow size 0 types, they can easily turn up as dependent types in generic code for which arrays can be made. IMO, this issue is a symptom of the language incorrectly conflating an address and an iterator. For example, in a language with size 0 types, the above code could be made to work perfectly fine if it considered an iterator into an array whose element type is size 0 to be something other than a pointer (i.e. simply an integer value corresponding to the array index). Once that separation is made, you can go back to easily iterating over the arrays no matter what the element size is, but the underlying implication is that any generic code in C++ today that assumes a pointer is a valid iterator into an array would now be broken for empty types. That might not be a huge deal if you don't allow "struct foo {};" to be a size 0 type, but that also rules out one of the main reasons that people want size 0 types to begin with. Allowing size 0 arrays might seem like an easier start, but I think you technically have to solve all of the same problems anyway, even though those problems happen to be less-likely to come up in practice.

Ultimately, I think that if we get size 0 types, we wouldn't just special case arrays of size 0. An array is just an object and it being size 0 requires the same considerations as other types being size 0. In the end, if we allow size 0 types, my personal conclusion is that:

1) It shouldn't be restricted to size 0 arrays
2) Different objects of the same (empty) type can share the same address
3) std::array_iterator< /*array type*/ > or something similar yields an appropriate iterator type for the array (isn't a pointer type when the element type is size 0)
4) [Unfortunately] for practicality, user-defined types should at least initially have some kind of specifier-hint that tells the implementation that the type is allowed to be size 0 if it happens to have no members or all of the members/bases are also size 0. This is subtle, but implementations all of a sudden having something like "struct foo {};" become size 0 overnight would never happen, and the same goes for struct foo { /*empty type members*/ };). Because of that, at least initially, people would have to explicitly request the ability for their type to potentially be size 0 when all other conditions allow it, otherwise they wouldn't be able to take advantage of it in practice during the transition.

In other words:

struct /*some specifier*/ foo { /*potentially some datamembers which may or may not be empty*/ };

where the existence of /*some specifier*/ allows the implementation to let "foo" be size 0.

Anyway, those were just my thoughts prior to putting things on hold, but I still stand by most of that assessment.

Nicol Bolas

unread,
Jul 7, 2016, 10:21:28 PM7/7/16
to ISO C++ Standard - Future Proposals, cala...@x.team
On Thursday, July 7, 2016 at 8:09:37 PM UTC-4, Matt Calabrese wrote:
On Mon, Jul 4, 2016 at 8:15 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

When I started working on Regular Void (p0146) I initially explored size 0 types but decided to postpone the effort because it has some difficult practical concerns regarding backwards compatibility. I think it might be possible that we will eventually get to a point where the language allows size 0 types, but it is not something that we can just start introducing (arrays or otherwise) without broader considerations. If the language had size 0 types to begin with then there would be no potential issues, but I half-suspect that the transition to a world where size 0 types exist may be more difficult than people would care to accept.

Here's a brief summary of my current conclusions. I'd like to eventually explore this again once Regular Void either goes into the language or is killed.

1) In order for size 0 types to be useful as a means of minimizing storage, we'd effectively have to change aliasing rules. No two objects of the same type can occupy the same address in C++ as it currently is, which user-level code can and does exploit.

Actually that's not quite true, and it is this exception that focused my stateless subobject idea (that I'm still occasionally refining). There are certain cases where two objects can have the same address. Namely, base class subobjects and their derived class. This is why [basic.lval]/10 has an explicit exception in the aliasing rules for types which are "similar to" the actual object's type.

The way I see it, the best way forward for stateless subobjects is not to make them zero-sized. It is to make them simply not take up space in the layout of the object they exist within. We permit their addresses to alias with anything, by adding them as an exception to the aliasing rules.

Namely, it is not undefined behavior to access the stored value of an object through a glvalue to a stateless subobject. This should be safe since, by definition, stateless objects have no value. Empty objects have a region of storage, but they cannot do anything with it. So it doesn't matter if their regions of storage overlap with someone else's.

This would require going through the standard and making changes where needed so that this doesn't break things. Like making trivial copying into a stateless subobject being undefined behavior (just as it is for copying into base subobjects of any kind), for example. Since stateless subobjects can overlap with anything, their addresses do not represent unique storage.

We'd want size 0 objects to able to [effectively] share an address. This is true even if you only limit size 0 types to arrays of size 0.
2) The more important concern, IMO, is that size 0 types fundamentally break our assumption that raw pointer types are able to act as iterators into arrays. Again, this problem still exists and needs to be... addressed... (heh) even if you only allow arrays of size 0 and not more general size 0 types.  A simple example of this is as follows:

// This iterates 5 times
int a[5] = {};
for( auto& elem : a )
{
  // ...do whatever...
}

// This iterates 0 times
using foo = int[0];
foo b[5] = {};
for( auto& elem : b )
{
  // ...do whatever...
}

The above might seem contrived, but it's not at all. Once you allow size 0 types, they can easily turn up as dependent types in generic code for which arrays can be made. IMO, this issue is a symptom of the language incorrectly conflating an address and an iterator. For example, in a language with size 0 types, the above code could be made to work perfectly fine if it considered an iterator into an array whose element type is size 0 to be something other than a pointer (i.e. simply an integer value corresponding to the array index). Once that separation is made, you can go back to easily iterating over the arrays no matter what the element size is, but the underlying implication is that any generic code in C++ today that assumes a pointer is a valid iterator into an array would now be broken for empty types. That might not be a huge deal if you don't allow "struct foo {};" to be a size 0 type, but that also rules out one of the main reasons that people want size 0 types to begin with. Allowing size 0 arrays might seem like an easier start, but I think you technically have to solve all of the same problems anyway, even though those problems happen to be less-likely to come up in practice.

Ultimately, I think that if we get size 0 types, we wouldn't just special case arrays of size 0. An array is just an object and it being size 0 requires the same considerations as other types being size 0. In the end, if we allow size 0 types, my personal conclusion is that:

1) It shouldn't be restricted to size 0 arrays
2) Different objects of the same (empty) type can share the same address
3) std::array_iterator< /*array type*/ > or something similar yields an appropriate iterator type for the array (isn't a pointer type when the element type is size 0)
4) [Unfortunately] for practicality, user-defined types should at least initially have some kind of specifier-hint that tells the implementation that the type is allowed to be size 0 if it happens to have no members or all of the members/bases are also size 0. This is subtle, but implementations all of a sudden having something like "struct foo {};" become size 0 overnight would never happen, and the same goes for struct foo { /*empty type members*/ };). Because of that, at least initially, people would have to explicitly request the ability for their type to potentially be size 0 when all other conditions allow it, otherwise they wouldn't be able to take advantage of it in practice during the transition.

I would say that the requirement should be stronger than that. Your idea is to make it a hint, that implementations may allow the size to be 0. I say that it should be enforced. If you declare that a type is zero-sized, then put things in it that aren't themselves zero-sized, then I would say that your code is incoherent and does not deserve to compile. It seems likely to me that a user who declares that a type will be zero-sized, then does something that makes this impossible has made a mistake.

But at the same time, I do think it is important that users can make a distinction between "This type will be zero-sided" and "If my declarations make this empty, then it should be zero-sized".
 
In other words:

struct /*some specifier*/ foo { /*potentially some datamembers which may or may not be empty*/ };

where the existence of /*some specifier*/ allows the implementation to let "foo" be size 0.

As I've refined my own stateless types idea, I came to realize something. While it is useful to be able to declare that a type will be stateless, it is also useful to declare that a type which was not explicitly declared stateless can be used as a stateless subobject. That way, you don't create a rigid separation between the world of stateless things and the world of non-stateless things.

If someone passes you an allocator that is an empty type, but they didn't declare it stateless, that's OK: you can fix that at the point of use.

Matt Calabrese

unread,
Jul 8, 2016, 12:50:00 AM7/8/16
to Nicol Bolas, ISO C++ Standard - Future Proposals
On Thu, Jul 7, 2016 at 7:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, July 7, 2016 at 8:09:37 PM UTC-4, Matt Calabrese wrote:
On Mon, Jul 4, 2016 at 8:15 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

When I started working on Regular Void (p0146) I initially explored size 0 types but decided to postpone the effort because it has some difficult practical concerns regarding backwards compatibility. I think it might be possible that we will eventually get to a point where the language allows size 0 types, but it is not something that we can just start introducing (arrays or otherwise) without broader considerations. If the language had size 0 types to begin with then there would be no potential issues, but I half-suspect that the transition to a world where size 0 types exist may be more difficult than people would care to accept.

Here's a brief summary of my current conclusions. I'd like to eventually explore this again once Regular Void either goes into the language or is killed.

1) In order for size 0 types to be useful as a means of minimizing storage, we'd effectively have to change aliasing rules. No two objects of the same type can occupy the same address in C++ as it currently is, which user-level code can and does exploit.

Actually that's not quite true, and it is this exception that focused my stateless subobject idea (that I'm still occasionally refining). There are certain cases where two objects can have the same address. Namely, base class subobjects and their derived class. This is why [basic.lval]/10 has an explicit exception in the aliasing rules for types which are "similar to" the actual object's type.

That's why I say two /different/ objects of the /same/ type. EBO doesn't cover that.

On Thu, Jul 7, 2016 at 7:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
I would say that the requirement should be stronger than that. Your idea is to make it a hint, that implementations may allow the size to be 0. I say that it should be enforced. If you declare that a type is zero-sized, then put things in it that aren't themselves zero-sized, then I would say that your code is incoherent and does not deserve to compile. It seems likely to me that a user who declares that a type will be zero-sized, then does something that makes this impossible has made a mistake.

It must be a hint to be useful in practice and it is not indicative of a mistake for the use-cases where it is desired. Basically, the end goal is that we want implementations to always prefer to make types such as "struct foo {};" or "struct foo { /*all size 0 datamembers*/ };" as size 0. The problem is that this wouldn't immediately happen in practice because it would be an ABI break. In order for users to actually be able to take advantage of this immediately during the transition, they'd need an additional way to declare types that didn't exist before (hence the specifier) -- this specifier doesn't have to even be a part of the standard, technically, but it would be an extension that I'd expect implementations to ultimately provide and that users would have to take advantage of in order to actually get size 0 types in practice.

The reason I say that the specifier must only be a hint and not cause an error if a user were to put a non-static datamember in the type that isn't size 0 is because if it caused an error then it wouldn't help for one of the primary use-cases, which is where datamembers have dependent types that may or may not be size 0 (i.e. a tuple template). You want to be able to declare something like a tuple template in such a way that its instantiations are size 0 when all datamembers are size 0, yet still have the same exact template definition when some of the dependent T types may not be size 0. Applying the specifier on the template definition there would do the trick if it really were just a hint, but if the "hint" were actually something that caused an error if the type couldn't actually be size 0, then the specifier wouldn't be usable there.

On Thu, Jul 7, 2016 at 7:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
As I've refined my own stateless types idea, I came to realize something. While it is useful to be able to declare that a type will be stateless, it is also useful to declare that a type which was not explicitly declared stateless can be used as a stateless subobject. That way, you don't create a rigid separation between the world of stateless things and the world of non-stateless things.

If someone passes you an allocator that is an empty type, but they didn't declare it stateless, that's OK: you can fix that at the point of use.

I'm not sure I follow what you mean. I don't think we are drawing a distinction between stateful and stateless things here. The idea is that an instance of a current C++ type and a size 0 type can be worked with via a common set of abstractions. If whatever we end up does not have such a set of abstractions, then we've failed.

Nicol Bolas

unread,
Jul 8, 2016, 9:22:20 AM7/8/16
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, cala...@x.team
On Friday, July 8, 2016 at 12:50:00 AM UTC-4, Matt Calabrese wrote:
On Thu, Jul 7, 2016 at 7:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, July 7, 2016 at 8:09:37 PM UTC-4, Matt Calabrese wrote:
On Mon, Jul 4, 2016 at 8:15 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
I have been following the recent thread about empty base optimization, or rather, the lack of empty member optimization possibility. And today I again fell in the 0 sized array trap in metaprogramming, noticing that it creates quite a few problems requiring extra coding.

When I started working on Regular Void (p0146) I initially explored size 0 types but decided to postpone the effort because it has some difficult practical concerns regarding backwards compatibility. I think it might be possible that we will eventually get to a point where the language allows size 0 types, but it is not something that we can just start introducing (arrays or otherwise) without broader considerations. If the language had size 0 types to begin with then there would be no potential issues, but I half-suspect that the transition to a world where size 0 types exist may be more difficult than people would care to accept.

Here's a brief summary of my current conclusions. I'd like to eventually explore this again once Regular Void either goes into the language or is killed.

1) In order for size 0 types to be useful as a means of minimizing storage, we'd effectively have to change aliasing rules. No two objects of the same type can occupy the same address in C++ as it currently is, which user-level code can and does exploit.

Actually that's not quite true, and it is this exception that focused my stateless subobject idea (that I'm still occasionally refining). There are certain cases where two objects can have the same address. Namely, base class subobjects and their derived class. This is why [basic.lval]/10 has an explicit exception in the aliasing rules for types which are "similar to" the actual object's type.

That's why I say two /different/ objects of the /same/ type. EBO doesn't cover that.

And my point is how you define "different". We could say that stateless subobjects are not different objects from any other object. Which makes sense, as they are stateless and therefore have no value representation.

On Thu, Jul 7, 2016 at 7:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
I would say that the requirement should be stronger than that. Your idea is to make it a hint, that implementations may allow the size to be 0. I say that it should be enforced. If you declare that a type is zero-sized, then put things in it that aren't themselves zero-sized, then I would say that your code is incoherent and does not deserve to compile. It seems likely to me that a user who declares that a type will be zero-sized, then does something that makes this impossible has made a mistake.

It must be a hint to be useful in practice and it is not indicative of a mistake for the use-cases where it is desired. Basically, the end goal is that we want implementations to always prefer to make types such as "struct foo {};" or "struct foo { /*all size 0 datamembers*/ };" as size 0. The problem is that this wouldn't immediately happen in practice because it would be an ABI break. In order for users to actually be able to take advantage of this immediately during the transition, they'd need an additional way to declare types that didn't exist before (hence the specifier) -- this specifier doesn't have to even be a part of the standard, technically, but it would be an extension that I'd expect implementations to ultimately provide and that users would have to take advantage of in order to actually get size 0 types in practice.

The reason I say that the specifier must only be a hint and not cause an error if a user were to put a non-static datamember in the type that isn't size 0 is because if it caused an error then it wouldn't help for one of the primary use-cases, which is where datamembers have dependent types that may or may not be size 0 (i.e. a tuple template). You want to be able to declare something like a tuple template in such a way that its instantiations are size 0 when all datamembers are size 0, yet still have the same exact template definition when some of the dependent T types may not be size 0. Applying the specifier on the template definition there would do the trick if it really were just a hint, but if the "hint" were actually something that caused an error if the type couldn't actually be size 0, then the specifier wouldn't be usable there.

That's why I said:

But at the same time, I do think it is important that users can make a distinction between "This type will be zero-sided" and "If my declarations make this empty, then it should be zero-sized".

For that tuple case, you need the latter. But for other cases, people need the former.
 

On Thu, Jul 7, 2016 at 7:21 PM, Nicol Bolas <jmck...@gmail.com> wrote:
As I've refined my own stateless types idea, I came to realize something. While it is useful to be able to declare that a type will be stateless, it is also useful to declare that a type which was not explicitly declared stateless can be used as a stateless subobject. That way, you don't create a rigid separation between the world of stateless things and the world of non-stateless things.

If someone passes you an allocator that is an empty type, but they didn't declare it stateless, that's OK: you can fix that at the point of use.

I'm not sure I follow what you mean. I don't think we are drawing a distinction between stateful and stateless things here. The idea is that an instance of a current C++ type and a size 0 type can be worked with via a common set of abstractions. If whatever we end up does not have such a set of abstractions, then we've failed.

My point is that whatever "common set of abstractions" you work out, this class definition must not be zero-sized:

struct empty{};

The reason being that it isn't zero-sized today, and making it zero-sized would represent a breaking, non-backwards-compatible change not only to itself but to every subobject it is used in. That's not acceptable for obvious reasons.

`std::allocator` is an empty class (probably). As such, if I want to make a member variable of that type which takes up no space, how do I do that? If you rely only on the type's definition to explicitly say that it is zero-sized, you can't. Thus, you divide the world into two divisions: code written after the standard changed, and code written before it changed.

Yet logically, any empty class ought to be able to be used in a zero-sized/stateless/whatever-you-call-it way, when declared as a subobjbect. Thus, any solution to this problem ought needs to also be able to be employed on a per-use basis.

Peter Koch Larsen

unread,
Jul 8, 2016, 11:58:32 AM7/8/16
to std-pr...@isocpp.org
Sean Parent in his key-note talk at cppnow proposed making void a
regular, empty type. At the same time he proposed that when inheriting
from void, you would have a truly empty type. I like that idea.

struct empty{}; // Not really empty.
struct truly_empty: void {}; //This one is!

static assert(sizeof(empty) == 1);
static assert(sizeof(truly_empty) == 0);

/Peter
> --
> You received this message because you are subscribed to the Google Groups
> "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.
> To view this discussion on the web visit
> https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/f3b99829-432a-47a4-b227-48e70cb18da5%40isocpp.org.

Bengt Gustafsson

unread,
Jul 9, 2016, 4:06:43 AM7/9/16
to ISO C++ Standard - Future Proposals
Avoiding these issues with syntax and semantics of declaring types that are zero sized was one of my original motivations for trying the idea of using a zero sized array of any type to get a zero sized variable of an empty type. 

As zero sized arrays are not allowed currently there is an open space for defining the exact semantics. As for Matt's original complaints these are valid for other ways to declare zero sized objects but not for zero sized arrays as they have never been allowed before. For instance it is obvious what a for loop over a zero sized array is to do: nothing. It is also easy to define what the size of an array of zero sized arrays is to mean: still an array of a total of zero elements, which follows the logic for all other sizes: multiply all dimension's sizes to get the total size. Furthermore the byte size of zero is consistent with the current way to calculate the byte size of an array variable: multply the align-adjusted element size with the element count of the array.

Zero sized arrays as such are also useful in the context of template code, where 0 may be a valid template parameter to instantiate with (such as for sentinels).


On the other hand the SFINAE example presented:

One of the ways the SFINAE-rule is actually used:
  void foo(char (*)[...evaluates to 0 or >0...] =0)
  void foo(...)   // other overloads
which is (currently) equivalent to (e.g.)
  std::disable_if<...evaluates to false/true...>::type foo();
  // other overloads
but would not, if zero-sized arrays are allowed (developers would have to replace "[value]" with "[2*(value)-1]" to get a negative-sized array for "false" to still fail SFINAE).
  
This seems to be something that people actually could have used before enable_if was defined.  Don't know if this is common enough to preclude introducing zero sized arrays, what do you think?


Nicol Bolas

unread,
Jul 9, 2016, 11:33:53 AM7/9/16
to ISO C++ Standard - Future Proposals


On Friday, July 8, 2016 at 11:58:32 AM UTC-4, Peter Koch Larsen wrote:
Sean Parent in his key-note talk at cppnow proposed making void a
regular, empty type. At the same time he proposed that when inheriting
from void, you would have a truly empty type. I like that idea.

struct empty{};  // Not really empty.
struct truly_empty: void {};  //This one is!

static assert(sizeof(empty) == 1);
static assert(sizeof(truly_empty) == 0);

/Peter

Right, but how does that resolve the problem in question? Namely, this:

template<typename Alloc>
struct Me
{
 
Alloc mine;
};

The point is that we want `mine` to not take up space if `Alloc` is an empty type. Your way puts the onus on the user to declare their `Alloc` as being derived from `void`. That's bad, because we cannot suddenly declare that `std::allocator` derives from `void`. That would break code.

Which means people would still have to use the EBO hack:

template<typename Alloc>
struct Me : public Alloc
{};

You've solved a particular problem, but you haven't solved this problem.

Nicol Bolas

unread,
Jul 9, 2016, 12:47:44 PM7/9/16
to ISO C++ Standard - Future Proposals
On Saturday, July 9, 2016 at 4:06:43 AM UTC-4, Bengt Gustafsson wrote:
Avoiding these issues with syntax and semantics of declaring types that are zero sized was one of my original motivations for trying the idea of using a zero sized array of any type to get a zero sized variable of an empty type.

Syntax is irrelevant; semantics is the main problem. At present, the semantics of your proposal are quite unclear. You say that zero-sized arrays are a thing, but you don't tell us how they really behave. Or rather, your explanation of their behavior is contradictory, broken, or just confused.

You still haven't answered one of the most important questions. Namely, what does this do:

int i[0];
int *j = i;
*j = 54;

Is that legal? If that's not legal, then how could this be legal:

Type t[0];
Type *j = t;
j
->SomeMemberFunction();

And if that isn't legal, how could you actually do anything with a zero-sized array? How do you access non-static members of such objects? How do you call member functions? And so forth.

The thing people forget about zero-size arrays in C is that they're... empty. If you have a zero-sized array of structs, you can't access members of them. And the whole point of this exercise is to have an object that takes up no space, but we can still access.

Also, you never answered my question from earlier in the thread: where does `j` point to? I've already proven that your desired answer (a pointer past the previous member) is non-functional. Such a pointer can alias with other objects if it falls outside the valid range of the type. And such aliasing is forbidden.

You say that you can give zero-sized arrays whatever semantics you want. But you don't have a coherent, detailed explanation for what those semantics are.
 
As zero sized arrays are not allowed currently there is an open space for defining the exact semantics. As for Matt's original complaints these are valid for other ways to declare zero sized objects but not for zero sized arrays as they have never been allowed before. For instance it is obvious what a for loop over a zero sized array is to do: nothing.

No. No, it isn't.

Take the following code, for some object `i`:

auto start = std::addressof(i);
auto end = ++start;
for(; start < end; ++start)
{...}

Now, this code is perfectly, 100% valid for any complete object `i`. Even if `i` were an array, the standard guarantees that this is legal. Not only is it legal for any complete object `i`, it has the same behavior: it will execute the loop exactly once. Even if `i` is an array, it will execute that loop once.

So if `i` were declared as a zero-sized array, some rule of C++ must either prevent this code from being legal or make `++start` not change the address of the pointer (which now means that the loop executes 0 times).

So, what is the rule you propose to change that does one of these two things? Are you going to declare that if you have a pointer to an object which is in a zero-sized array, it is undefined behavior to perform pointer arithmetic on it? Are you going to declare that taking the address of a zero-sized array is il-formed entirely?

What is your solution to this problem?

It is also easy to define what the size of an array of zero sized arrays is to mean: still an array of a total of zero elements, which follows the logic for all other sizes: multiply all dimension's sizes to get the total size. Furthermore the byte size of zero is consistent with the current way to calculate the byte size of an array variable: multply the align-adjusted element size with the element count of the array.

OK, so if I have this:

T t[20];

int i = 0;
for(T &tref : t)
{
 
++i;
}

Now, given this as a definition of `T`:

using T = int[0];

Will `i` be 20 or 0 after executing the above code? Because for any non-zero-sized type `T`, it's guaranteed to be 20. Whereas `begin` and `end` for an array of zero-sized elements must be the same pointer.

Matt Calabrese

unread,
Jul 11, 2016, 1:34:52 PM7/11/16
to ISO C++ Standard - Future Proposals
On Sat, Jul 9, 2016 at 1:06 AM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:
As zero sized arrays are not allowed currently there is an open space for defining the exact semantics. As for Matt's original complaints these are valid for other ways to declare zero sized objects but not for zero sized arrays as they have never been allowed before. For instance it is obvious what a for loop over a zero sized array is to do: nothing. It is also easy to define what the size of an array of zero sized arrays is to mean: still an array of a total of zero elements, which follows the logic for all other sizes: multiply all dimension's sizes to get the total size. Furthermore the byte size of zero is consistent with the current way to calculate the byte size of an array variable: multply the align-adjusted element size with the element count of the array.

I believe you may have missed what I was trying to get across. Things like arrays of empty types still exist when you attempt to restrict size 0 types to arrays. To reiterate, for example, just like other types, you can make arrays of arrays (a multidimensional array in C++ is just an array of arrays). Once you have arrays of size 0, you can make arrays of empty types because you can make arrays whose element type is array of size 0. However rare that may seem, it is the same problem that you encounter with more general size 0 types and needs to be explicitly addressed. In other words, while limiting things to size 0 arrays you may make it less common for people to ask questions about corner cases, but those questions still need to have answers when creating the specification. That answer should make sense and be consistent with whatever we'd decide for further size 0 types, so I don't think we're realistically making things easier by keeping things restricted to size 0 arrays. Most of the harder questions still need the same kinds of answers.

Matt Calabrese

unread,
Jul 11, 2016, 1:58:20 PM7/11/16
to ISO C++ Standard - Future Proposals
On Sat, Jul 9, 2016 at 9:47 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Saturday, July 9, 2016 at 4:06:43 AM UTC-4, Bengt Gustafsson wrote:
Avoiding these issues with syntax and semantics of declaring types that are zero sized was one of my original motivations for trying the idea of using a zero sized array of any type to get a zero sized variable of an empty type.

Syntax is irrelevant; semantics is the main problem. At present, the semantics of your proposal are quite unclear. You say that zero-sized arrays are a thing, but you don't tell us how they really behave. Or rather, your explanation of their behavior is contradictory, broken, or just confused.

You still haven't answered one of the most important questions. Namely, what does this do:

int i[0];
int *j = i;
*j = 54;

Is that legal? If that's not legal, then how could this be legal:

Type t[0];
Type *j = t;
j
->SomeMemberFunction();

And if that isn't legal, how could you actually do anything with a zero-sized array? How do you access non-static members of such objects? How do you call member functions? And so forth.

I agree with the rest of your post, but I think you're missing the point of size 0 arrays in your examples above. None of those should be legal. You're accessing the one-past-the-end element of an array. Size 0 arrays are useful when the size is, for instance, dependent on a template parameter. In fact, std::array already handles this properly in at least some ways -- you can make a std::array with 0 elements and you (appropriately) cannot access the element at index 0. As well, a std::array of N elements whose element type is a std::arrays of 0 elements also implicitly behaves correctly with respect to iteration (though the element type obviously is not currently size 0 in any [compliant] implementation). What use-cases are you thinking of where you believe you'd want to actually access a logically non-existent element?

Anyway, something like the std::array-style behavior is the kind of behavior we should aspire to have for raw arrays of length 0, although the only feasible way that I personally see to do this would be to state that an iterator into an array whose element type is size 0 is not a pointer to that element type. Instead, it should be some other type, such as one obtained by the hypothetical alias std::array_iterator< T[0] >. Similarly, that is the iterator type that the array should decay to (assuming people still want decay in order to be partially consistent with all other array types). What this would change for users is that, when writing generic code, if you ever create a raw array and need to iterate over it, or if you want to iterate over a [logically] contiguous container whose element type may or may not be a size 0 type, you'd need to use the generalized iterator alias instead of a pointer directly. In the case of array types that are not size 0, the iterator alias would (or at least could) be an alias of the normally-used pointer type.

Nicol Bolas

unread,
Jul 11, 2016, 2:05:07 PM7/11/16
to ISO C++ Standard - Future Proposals, cala...@x.team
On Monday, July 11, 2016 at 1:58:20 PM UTC-4, Matt Calabrese wrote:
On Sat, Jul 9, 2016 at 9:47 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Saturday, July 9, 2016 at 4:06:43 AM UTC-4, Bengt Gustafsson wrote:
Avoiding these issues with syntax and semantics of declaring types that are zero sized was one of my original motivations for trying the idea of using a zero sized array of any type to get a zero sized variable of an empty type.

Syntax is irrelevant; semantics is the main problem. At present, the semantics of your proposal are quite unclear. You say that zero-sized arrays are a thing, but you don't tell us how they really behave. Or rather, your explanation of their behavior is contradictory, broken, or just confused.

You still haven't answered one of the most important questions. Namely, what does this do:

int i[0];
int *j = i;
*j = 54;

Is that legal? If that's not legal, then how could this be legal:

Type t[0];
Type *j = t;
j
->SomeMemberFunction();

And if that isn't legal, how could you actually do anything with a zero-sized array? How do you access non-static members of such objects? How do you call member functions? And so forth.

I agree with the rest of your post, but I think you're missing the point of size 0 arrays in your examples above. None of those should be legal.

Right, so... what's the point of having a zero-sized array? Remember the stated goal: to allow you to have objects which takes up no space. If `a[0]` for a zero sized array `a` is always illegal, how could you access the object?

If you can't access the object, then the concept doesn't solve the problem it is intended to solve. Remember: his suggestion is not intended to make code more generic. He explicitly wants to allow zero-sized arrays to allow you to have empty types which take up no space as member subobjects. Which requires that a zero-sized array may still have a legitimate object behind it.

Matt Calabrese

unread,
Jul 11, 2016, 2:17:13 PM7/11/16
to Nicol Bolas, ISO C++ Standard - Future Proposals
On Mon, Jul 11, 2016 at 11:05 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Monday, July 11, 2016 at 1:58:20 PM UTC-4, Matt Calabrese wrote:
On Sat, Jul 9, 2016 at 9:47 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Saturday, July 9, 2016 at 4:06:43 AM UTC-4, Bengt Gustafsson wrote:
Avoiding these issues with syntax and semantics of declaring types that are zero sized was one of my original motivations for trying the idea of using a zero sized array of any type to get a zero sized variable of an empty type.

Syntax is irrelevant; semantics is the main problem. At present, the semantics of your proposal are quite unclear. You say that zero-sized arrays are a thing, but you don't tell us how they really behave. Or rather, your explanation of their behavior is contradictory, broken, or just confused.

You still haven't answered one of the most important questions. Namely, what does this do:

int i[0];
int *j = i;
*j = 54;

Is that legal? If that's not legal, then how could this be legal:

Type t[0];
Type *j = t;
j
->SomeMemberFunction();

And if that isn't legal, how could you actually do anything with a zero-sized array? How do you access non-static members of such objects? How do you call member functions? And so forth.

I agree with the rest of your post, but I think you're missing the point of size 0 arrays in your examples above. None of those should be legal.

Right, so... what's the point of having a zero-sized array? Remember the stated goal: to allow you to have objects which takes up no space. If `a[0]` for a zero sized array `a` is always illegal, how could you access the object?

You can access the array object perfectly fine, just not an element of the array object, because it doesn't actually have any elements. Examples of things you can logically do with an array of 0 elements are that you can pass the object itself around by reference, you can create iterators into the array (they would be end iterators), you can have a template definition where the array size is dependent and the definition iterates over the array (the code wouldn't fail to compile for the size 0 case and would behave as would be consistent for all other array sizes). Also, depending on how it's specified and if aliasing rules are relaxed for size 0 types, a type that contains a size 0 array (most-likely an instantiation of a template such as std::array) would not contribute to the overall size of the encapsulating object in a way that is somewhat analogous to EBO.

Maybe this isn't what some people want it to be, but I'd argue that this is what it logically should be (though aliasing aspects are still a bit subjective).

Nicol Bolas

unread,
Jul 11, 2016, 2:42:25 PM7/11/16
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, cala...@x.team
On Monday, July 11, 2016 at 2:17:13 PM UTC-4, Matt Calabrese wrote:
You can access the array object perfectly fine, just not an element of the array object, because it doesn't actually have any elements. Examples of things you can logically do with an array of 0 elements are that you can pass the object itself around by reference, you can create iterators into the array (they would be end iterators), you can have a template definition where the array size is dependent and the definition iterates over the array (the code wouldn't fail to compile for the size 0 case and would behave as would be consistent for all other array sizes). Also, depending on how it's specified and if aliasing rules are relaxed for size 0 types, a type that contains a size 0 array (most-likely an instantiation of a template such as std::array) would not contribute to the overall size of the encapsulating object in a way that is somewhat analogous to EBO.

Maybe this isn't what some people want it to be, but I'd argue that this is what it logically should be (though aliasing aspects are still a bit subjective).

I agree. But that behavior is A) not the behavior the OP wants, and B) not solving the problem the OP wants to use the behavior to solve.

So that would be one more reason not to implement stateless objects as zero-sized arrays.
Reply all
Reply to author
Forward
0 new messages