It seems to me that by requiring that the type of the variable being declared stateless really is empty you prevent your new feature from being used in maybe the most common use case exemplified by the container Alloc type. What's needed there is a potentially zero-sized member in case the Alloc class is stateless, but which takes up as much space as needed if it is not.
template<typename Alloc>
struct DataBlock
{
stateless(is_empty_v<Alloc>) Alloc a;
};
struct OtherType
{
stateless First f;
Second s; //Oops, forgot to declare that stateless.
};
struct MyType
{
stateless OtherType o;
};
I commend you for pointing out that even classes with virtual methods can be empty, but maybe your paper should elaborate a little on why: You think of such an object as containing only a vtbl pointer but as it is a by-value member of its containing object you don't need the vtbl pointer as you know what it points to for each method anyway.
Your idea of allowing a class declaration to be declared stateless is scary as it removes the declaration of the statelessness from the usage. Now you can declare a std::vector<T> with a stateless T and, well, it is hard to tell if this would work. If sizeof(T) is 1 for a stateless T it could but would waste heap memory.
As long as the stateless property is on the variable the code accessing the variable can work with the restrictions that this qualifier introduces.
I don't see the problem with array iteration that you state " If the stateless array has a count larger than 1, and it is contained in an empty class, there is no guarantee that adding a number larger than 1 will result in a valid address for the purposes of iteration."The address is going to be just as valid as for any iteration. Yes, the address can point to memory that would cause an access violation if accessed, but there is no state to access there anyway, so doing so deserves an access violation. What could possibly happen is that if you declare a very large array of zero sized objects the address could wrap around causing loops with < end conditions to stop immediately. Is this what you mean? I would say that this case is arcane enough to let through with a recommendation: Don't use < tests on loop pointers when iterating over stateless objects. (Here again is a difference between a stateless variable and a stateless class: if stateless claesses are allowed this has to be extended to: Don't use < tests in loops in template code). In any case, as you mention: The this pointer for any method call inside the loop body would not care if the address was actually outside the memory area (0 bytes) allocated for the array... it will not use the this pointer anyway.
Em quarta-feira, 13 de julho de 2016, às 15:35:22 PDT, Bengt Gustafsson
escreveu:
> As far as C++ is concerned, if you do `a->v()`, you are accessing the
> pointer `a`. Oh, the compiler may not dereference that memory location just
> to call the function (unless it's virtual). But this isn't about what
> compilers do; it's about what the standard *says*. And `a->v()` is no less
> of an access to the pointer `a` than `a->d = 4;` is.
>
> If this is what worries you I think the C++ standard could easily be
> changed to allow calling a non-virtual method of a stateless object with
> any this pointer. If this solves the "array of stateless objects" issue it
> is well worth it.
Only if you can't access "this" pointer in a stateless class's non-static
member function.
We may want to do that anyway, as otherwise the this pointer value could be
used for keying access in an associative container, which in turn would
require two different stateless objects to have different this pointer values.
You wrote:As far as C++ is concerned, if you do `a->v()`, you are accessing the pointer `a`. Oh, the compiler may not dereference that memory location just to call the function (unless it's virtual). But this isn't about what compilers do; it's about what the standard says. And `a->v()` is no less of an access to the pointer `a` than `a->d = 4;` is.If this is what worries you I think the C++ standard could easily be changed to allow calling a non-virtual method of a stateless object with any this pointer. If this solves the "array of stateless objects" issue it is well worth it.
I understand now that the need for a stateless class came from usages of new, make_unique and similar where it is hard or impossible to tag the variable itself as stateless.
stateless struct no_state {};
struct stateful{};
If you ask me we could just as well make all variables stateless(auto), i.e. I don't see the problem (outside the standard text) of having the same address of two variables, but everyone else seems convinced that this is a real problem, and in that case I would be scared to put stateless on a class and then use it as a template parameter so that the template code unknowingly generates zero sized variables... It seems that you have to make up your mind whether aliasing the address of two variables is a problem or not! I don't think sprinkling the documentation of all libraries out there with notes on each template whether it is proof for stateless classes or not is a feasible solution, and then expecting users to read and follow those notes.
You wrote:As far as C++ is concerned, if you do `a->v()`, you are accessing the pointer `a`. Oh, the compiler may not dereference that memory location just to call the function (unless it's virtual). But this isn't about what compilers do; it's about what the standard says. And `a->v()` is no less of an access to the pointer `a` than `a->d = 4;` is.If this is what worries you I think the C++ standard could easily be changed to allow calling a non-virtual method of a stateless object with any this pointer. If this solves the "array of stateless objects" issue it is well worth it.
stateless Foo fooArray[7];
for (auto begin = fooArray, end = fooArray + 7; begin != end; ++begin) {
bar(*begin);
}
On Wednesday, July 13, 2016 at 7:03:42 AM UTC-4, Bengt Gustafsson wrote:It seems to me that by requiring that the type of the variable being declared stateless really is empty you prevent your new feature from being used in maybe the most common use case exemplified by the container Alloc type. What's needed there is a potentially zero-sized member in case the Alloc class is stateless, but which takes up as much space as needed if it is not.
I called that specific case out as an example:
`a` will take up no space if `Alloc` is empty. Also, you could use `stateless(auto)` instead of the explicit `is_empty_v` test.
This allows you to make a distinction between "Make this object stateless where possible" and "If this type is not empty, then that's an error." While the former is useful, the latter is also quite useful. After all, if your type needs to be empty, and you're including a bunch of other member types, you want to make sure nobody breaks your type's rules by accidentally adding an NSDM or something.
Em quarta-feira, 13 de julho de 2016, às 16:25:47 PDT, Nicol Bolas escreveu:
> > We may want to do that anyway, as otherwise the this pointer value could
> > be
> > used for keying access in an associative container, which in turn would
> > require two different stateless objects to have different this pointer
> > values.
>
> The problem is that your suggestion makes it impossible to have stateless
> subobjects which were not initially declared that way. And therefore it
> doesn't actually solve the whole problem we're trying to solve
> (`std::allocator` would not be able to be used statelessly except via EBO).
> So we're back to the same problem.
Why can't std::allocator be declared in C++2x as stateless?
Granted, this is not very common, but why not require class authors to add
"stateless" ?
> Which suggests a possible solution, actually. We could just forbid allowing
> a type to have more than one stateless subobject of the same type,
> recursively.
>
> But really, I think it's worth just accepting this particular edge case. I
> think it's reasonable to assume that 99% of empty types do not use `this`
> in this way, and the 1% who do need such identity can just give themselves
> a `char` member.
They probably should, but we have two options:
1) require "fake stateless" classes to have a member
2) require stateless classes to have a "stateless" keywords
Why not #2?
template<typename Alloc>
struct DataBlock : public Alloc
{};
template<typename Alloc>
struct DataBlock
{
Alloc a;
};
struct empty {};
struct not_standard_layout : public empty
{
empty e;
};
stateless struct no_identity {};
struct empty {};
struct s1
{
no_identity n1; //Stateless by default
stateless empty e1; //Explicitly stateless.
no_identity n2; //OK, n1 and n2 are aliases of the same object.
stateless empty e2; //il-formed. `empty` was not declared `stateless`, and therefore it must have identity.
};
However, this is merely a contribution to the syntactic bikeshed; I agree with Nicol that there's no obvious forward progress on the semantic problem happening here.
For example:
stateless struct no_identity {};
struct empty {};
struct s1
{
no_identity n1; //Stateless by default
stateless empty e1; //Explicitly stateless.
no_identity n2; //OK, n1 and n2 are aliases of the same object.
stateless empty e2; //il-formed. `empty` was not declared `stateless`, and therefore it must have identity.
};
On quinta-feira, 14 de julho de 2016 13:09:36 PDT Nicol Bolas wrote:
> > Why can't std::allocator be declared in C++2x as stateless?
>
> Because that would potentially break code. The size of any object that used
> `allocator` as a member variable would change, thus breaking any ABIs
> involving such objects.
How? If we require both in the declaration of the class and where it's used,
then no current code would be affected.
> > They probably should, but we have two options:
> >
> > 1) require "fake stateless" classes to have a member
> > 2) require stateless classes to have a "stateless" keywords
> >
> > Why not #2?
>
> I do appreciate the simplicity of #2. It was after all my original design
> from the very first version of this idea
> <https://groups.google.com/a/isocpp.org/forum/#!searchin/std-proposals/state
> less$20inner/std-proposals/HjGujSdKXX0/27WIWTc_EwAJ>. However, remember back
> to the defining problem for this proposal. Someone gives us a type. We want
> to use that type as a member variable. But if that type is empty, we don't
> want our type taking up extra space.
But only if the class author declares that it shouldn't take up space. If the
class author requires that the object have a unique pointer address, different
from any and all other objects, then you cannot ignore her.
> #2 is not a solution to this problem. Why? Because people will still pass
> us empty-but-not-stateless types. And therefore this:
>
> template<typename Alloc>
> struct DataBlock : public Alloc
> {};
>
> Will always be a better alternative than this:
>
> template<typename Alloc>
> struct DataBlock
> {
> Alloc a;
> };
>
> In the former, we are guaranteed to avoid bloat for any empty class. In the
> latter, we only avoid bloat if the user provides a stateless type. And
> stateless types will *always* be a subset of empty types.
But you don't know if you're allowed to "avoid bloat". Maybe your (premature)
optimisation will break code later.
I really think that the class author should be allowed to decide whether the
use of that class can take up no space.
> For example:
>
> stateless struct no_identity {};
> struct empty {};
>
> struct s1
> {
> no_identity n1; //Stateless by default
> stateless empty e1; //Explicitly stateless.
> no_identity n2; //OK, n1 and n2 are aliases of the same object.
> stateless empty e2; //il-formed. `empty` was not declared `stateless`,
> and therefore it must have identity.
> };
That's the green-field case. It's the cleanest solution, I agree, but that
doesn't allow us to use it for std::allocator.
template<typename T> class Class {
stateless T first[4]; // 0 bytes
int x; // 4 bytes
stateless T second; // 1 byte
};
Thanks a lot for this. I've been meaning to come back to it
"later", but your proposal is better than anything I would have
come up with.
Here's a small update to the idea, after receiving commentary. It explains the behavior of stateless types hopefully more explicitly. It also adds a notation that stateless subobjects still impose their alignments on their containing types. So if you declare that an empty class has 8 byte alignment, then classes which statelessly use it will have at least 8 byte alignment. Even though it doesn't take up size.
Also, I've been thinking about complexity reduction (this isn't reflected in the new version). I'm starting to think that `stateless` doesn't need full constant-expression conditionals.
At subobject declarations, there are 3 scenarios of interest: 1) The user requires the subobject to be stateless and therefore the type must be empty. 2) The user wants the subobject to be stateless if the type allows that, but if not then it won't be stateless. 3) The user doesn't care.
With type declarations, there are 3 scenarios of interest: 1) The user wants all potentially stateless uses of the type to be stateless, so fail if the type is non-empty. 2) The user wants all potentially stateless uses of the type to be stateless if the type is empty, but otherwise be non-stateless. 3) The user does not want all potentially stateless uses of the type to be stateless.--
Even something like `std::pair` doesn't need a conditional. A `pair<stateless1, stateless2>` is an empty type, and therefore we could declare that `pair` would be stateless if both types passed to it are stateless.
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/3ffd6df6-ba77-414c-aeef-7ec23206bb88%40isocpp.org.
When it comes to arrays of stateless objects I think there is a fourth option worth considering:4. arrays of stateless objects take no room in the declared scope, but pointer arithmethics and indexing works as if sizeof(T) is 1, (which indeed it is).
This allows loops over the array elements and pointer arithmetic and similar operations to work as for any type. Of course you can't actually access the array elements, but this doesn't matter as there is nothing there anyway. I can't see that it would be worse to, say, do this:
template<typename Alloc>
struct Block
{
stateless(auto) Alloc a;
};
Sorry, that got sent before it was complete...I was aiming to point out that the this pointer when calling the method on the scalar s is just as invalid as when calling it for arr[1].Oh, right now I see that this may cause an aliasing problem if the array address range goes outside the surrounding object, and then for instance two of those objects are allocated after each other on the heap.
This brings up an idea I have had in the back of my head for a couple of days:Couldn't it just be demanded from the compiler to guarantee that no two objects of the same type have the same address? Here is an example:
This also touches on the discussion of inheriting from T to get EBO and then having the first NSDM being a T too, which creates an alias situation today, which this rule would (potentially) remove.
This rule could also be refined to guarantee that there is no aliasing between stateless variables and other variables even if they have different types:
I think we are definitely making progress, but it is still unclear if this reasoning leads all the way to a useful proposal. My conclusions are that 'stateless' on class and variable levels carry part of the semantic meaning and that all four combinations are different:
On Sunday, July 17, 2016 at 6:59:43 AM UTC-4, Bengt Gustafsson wrote:Couldn't it just be demanded from the compiler to guarantee that no two objects of the same type have the same address? Here is an example:
No. That would mean that `stateless` becomes a suggestion, not a requirement. That's bad. We're trying to improve EBO, not take its bad ideas.
But in any case, if someone uses `stateless`, they should mean it. And if it can't be stateless, then it should be a hard error. After all, this is all about being able to control the layout of a type. If your layout controls are optional, why bother?
On Tue, Jul 19, 2016 at 12:44 PM, Nicol Bolas <jmck...@gmail.com> wrote:On Sunday, July 17, 2016 at 6:59:43 AM UTC-4, Bengt Gustafsson wrote:Couldn't it just be demanded from the compiler to guarantee that no two objects of the same type have the same address? Here is an example:
No. That would mean that `stateless` becomes a suggestion, not a requirement. That's bad. We're trying to improve EBO, not take its bad ideas.
But in any case, if someone uses `stateless`, they should mean it. And if it can't be stateless, then it should be a hard error. After all, this is all about being able to control the layout of a type. If your layout controls are optional, why bother?All "layout controls" in C++ are optional right now, because the implementation is permitted to insert padding bytes anywhere it pleases (even in a standard-layout POD struct). Yes, yes, the members have to be in a certain order and the first member must be at offset 0; but if the compiler chooses to put your three consecutive S-sized objects at offsets S+1, 2S+2, and 3S+47, there's really nothing you can do about it except complain about its quality of implementation. I suppose this property would continue to hold even when S==0.So I think Bengt's idea deserves more than just a quick writeoff; I think it's potentially useful.
struct empty{};
struct foo
{
int x;
};
struct bar1 : public empty
{
int y;
};
struct bar2
{
int z;
stateless empty e;
};
> It would also prevent implementations from having null conversions to a particular stateless> subobject (that is, keeping the pointer the same as the container). And keeping that door> open is important for other features I want to see.Givenstruct S { stateless M m1; stateless M m2; int x; stateless M m3; };
do you mean that you wantassert( (void*) &s == (void*) &s.m1 ); // I approve of thisorassert( (void*) &s == (void*) &s.m2 ); // I think this is sketchy only because m1 and m2 have the same type// Bengt's suggestion is to force the implementation to insert a padding byte between m1 and m2// so as to preserve this assertion's falsenessorassert( (void*) &s == (void*) &s.m3 ); // I think this is flat-out crazy, fwiw?
Also, either the mention of "other features I want to see" is a distraction, or else it's relevant; if it's relevant, could you explain and/or link to an explanation of those features?