Specifier to cause the destructor to be called at last use instead of end of scope

287 views
Skip to first unread message

nicolas...@gmail.com

unread,
Aug 24, 2016, 2:51:01 PM8/24/16
to ISO C++ Standard - Future Proposals
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?
- Nicolas

Ville Voutilainen

unread,
Aug 24, 2016, 3:01:34 PM8/24/16
to ISO C++ Standard - Future Proposals
On 24 August 2016 at 21:51, <nicolas...@gmail.com> wrote:
> Hi all,
>
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.

Define "last use".

> Limiting the scope with extra braces or by putting the operations in a
> subroutine also often isn't a desirable workaround. Note that compilers have

..because..?

Pablo Oliva

unread,
Aug 24, 2016, 3:10:04 PM8/24/16
to std-pr...@isocpp.org
I don't quite get why simply calling the normal destructor for the object wouldn't achieve the optimization goals you pretend.

What you seem to be suggesting is that we can signal the complier that an object should be destroyed as soon as possible. This seems to be more of a quality of implementation issue on the compiler's side than something that needs to be standardized. And it doesn't need a different destructor.

Consider asking your compiler vendor for this as an enhancement, or consider implementing it yourself in an open source compiler. There just doesn't seem to be anything in this (interesting) idea that should be taken care of by the standard's committee, unless I have misunderstood it.

--
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-proposals+unsubscribe@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/ec2e95cc-3ef0-435b-aaf9-13e982c4583e%40isocpp.org.

Bernd Lörwald

unread,
Aug 24, 2016, 3:16:52 PM8/24/16
to std-pr...@isocpp.org

> Am 24.08.2016 um 21:01 schrieb Ville Voutilainen <ville.vo...@gmail.com>:
>
>> Limiting the scope with extra braces or by putting the operations in a
>> subroutine also often isn't a desirable workaround. Note that compilers have
>
> ..because..?

It might sometimes be difficult to express the lifetimes as short as possible:

resource a; // begin minimum lifetime a
resource b; // begin minimum lifetime b
last_use (a); // end minimum lifetime a
long_task (b);
last_use (b); //end minimum lifetime b
// end current lifetime b
// end current lifetime a

The explicit braces would require pain:

auto&& b {[&]{
resource a; // begin minimum lifetime a
resource b; // begin minimum lifetime b
lase_use (a); // end minimum lifetime a
return b;
// end current lifetime a
}()};
long_task (b);
last_use (b); //end minimum lifetime b
// end current lifetime b

You really don’t want to write that for more than one variable, do you?

Nicol Bolas

unread,
Aug 24, 2016, 3:17:47 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, August 24, 2016 at 3:10:04 PM UTC-4, Pablo Oliva wrote:
I don't quite get why simply calling the normal destructor for the object wouldn't achieve the optimization goals you pretend.

Because that would provoke UB. If you explicitly call the destructor for an automatic object, the system will still call the destructor at the end of scope. Unless you reconstructed the object in that memory beforehand, you will get UB.

What you seem to be suggesting is that we can signal the complier that an object should be destroyed as soon as possible. This seems to be more of a quality of implementation issue on the compiler's side than something that needs to be standardized. And it doesn't need a different destructor.

The destructor for a non-trivially destructible automatic object is called at the end of scope, and will be called in the reverse order of construction. As such, the side-effects cannot happen before the end of scope. So there's no QOI here; the standard does not permit a non-trivially destructible automatic object to be destroyed before the end of scope or out-of-order.

Matt Calabrese

unread,
Aug 24, 2016, 3:19:24 PM8/24/16
to ISO C++ Standard - Future Proposals
While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious), I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.

Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.

What I'm saying is that while the notion you describe is useful, it effectively always needs to be explicit in some form at the point of desired destruction in a language that has pointers and references, like C++. What you'd do is, when the *user* is done with the object, they'd notify the compiler -- this would happen somewhat automatically with something like an explicit destructive move operation if we had them, but the idea is more general than move operations. Once such a destructive operation is used, the compiler would diagnose any in-scope uses after that point, and it would no longer run the destructor at the end of scope. Further considerations need to be there as well, such as the fact that if your annotated destructive operation occurs in a nested branch, all other branches must also have such destructive operations, etc.

Nicol Bolas

unread,
Aug 24, 2016, 3:21:23 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, August 24, 2016 at 3:16:52 PM UTC-4, Bernd Lörwald wrote:

> Am 24.08.2016 um 21:01 schrieb Ville Voutilainen <ville.vo...@gmail.com>:
>
>> Limiting the scope with extra braces or by putting the operations in a
>> subroutine also often isn't a desirable workaround. Note that compilers have
>
> ..because..?

It might sometimes be difficult to express the lifetimes as short as possible:

  resource a; // begin minimum lifetime a
  resource b; // begin minimum lifetime b
  last_use (a); // end minimum lifetime a
  long_task (b);
  last_use (b); //end minimum lifetime b
  // end current lifetime b
  // end current lifetime a
 

And how do you know that `b` isn't using `a`? That's the point of Ville's "Define 'last use'." question. You cannot just look at code and know that `a` is finished being used when the locally visible code stops using it.

Destruction of automatic objects in the reverse order of their construction is a guarantee that the standard should not arbitrarily discard.

Bernd Lörwald

unread,
Aug 24, 2016, 3:25:59 PM8/24/16
to std-pr...@isocpp.org

> And how do you know that `b` isn't using `a`? That's the point of Ville's "Define 'last use'." question. You cannot just look at code and know that `a` is finished being used when the locally visible code stops using it.

I understand determining „last use“ is hard, which is why I explicitly wrote „last_use()“ and did not let them depend on each other in initialization. Detecting „now unused until dtor“ is of course hard and non-local. It is probably also more likely that the compiler would be better at detecting it than most humans are.

> Destruction of automatic objects in the reverse order of their construction is a guarantee that the standard should not arbitrarily discard.

I am a huge fan of automatic storage and reverse order destruction, which I instantly miss in other languages. I was just trying to give an example as to why doing it explicitly is not trivial either.

Matt Calabrese

unread,
Aug 24, 2016, 3:31:53 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wed, Aug 24, 2016 at 12:19 PM, Matt Calabrese <cala...@x.team> wrote:
What I'm saying is that while the notion you describe is useful, it effectively always needs to be explicit in some form at the point of desired destruction in a language that has pointers and references, like C++. What you'd do is, when the *user* is done with the object, they'd notify the compiler -- this would happen somewhat automatically with something like an explicit destructive move operation if we had them, but the idea is more general than move operations. Once such a destructive operation is used, the compiler would diagnose any in-scope uses after that point, and it would no longer run the destructor at the end of scope.

Technically it would just be UB, much like functions encountering return statements, but in practice it, too, would be properly diagnosed in the vast majority of cases.

Thiago Macieira

unread,
Aug 24, 2016, 3:52:59 PM8/24/16
to std-pr...@isocpp.org
On quarta-feira, 24 de agosto de 2016 11:51:01 PDT nicolas...@gmail.com
wrote:
> The use of this would be to free resources held by the object sooner. This
> could be heap memory, file handles, mutex locks, etc. Releasing them as
> soon as we're done with the object that represents them would result in
> more efficient programs.

Locks are the perfect counter-example to what you're proposing. The compiler
simply cannot tell what the "last use" is:

std::lock_guard lock(mutex);
i = 1;

Where was the last use: before or after the assignment to i?

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

Thiago Macieira

unread,
Aug 24, 2016, 3:56:19 PM8/24/16
to std-pr...@isocpp.org
On quarta-feira, 24 de agosto de 2016 12:19:20 PDT 'Matt Calabrese' via ISO C+
+ Standard - Future Proposals wrote:
> For example, consider one of the kinds of types that you explicitly called
> out: locks. A lock declaration has no actual knowledge of the memory that
> it is effectively guarding, so there is no way to *actually* know when the
> object is truly done with. Indeed, often times the object isn't even
> touched directly *at all* once constructed. Prematurely destroying such an
> object would be horrible. This issue is true of basically any object that
> has a relationship with another object, such as iterators and their
> associated container.

We can go further: files can be locks too, locking something that may not even
be under control of the current process. There's simply nothing to be
annotated.

nicolas...@gmail.com

unread,
Aug 24, 2016, 4:16:30 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, August 24, 2016 at 3:01:34 PM UTC-4, Ville Voutilainen wrote:
On 24 August 2016 at 21:51,  <nicolas...@gmail.com> wrote:
> Hi all,
>
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.

Define "last use".

The last use according to liveness analysis.
 
> Limiting the scope with extra braces or by putting the operations in a
> subroutine also often isn't a desirable workaround. Note that compilers have

..because..?

The code would become littered with braces if you want to limit the scope of each object to its miminal range, and it would be annoying to maintain when refactoring the code. Just imagine having to do it for scalar variables to avoid register spilling. Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation. I'd like to see that extended to the resources managed by objects.

Nevin Liber

unread,
Aug 24, 2016, 4:22:38 PM8/24/16
to std-pr...@isocpp.org
On 24 August 2016 at 15:16, <nicolas...@gmail.com> wrote:
The code would become littered with braces if you want to limit the scope of each object to its miminal range,

I don't understand why you would want to do that.  Could you elaborate?

And if the objects are truly independent and have no observable effect, can't a compiler do that under the as-if rule?

Also, if you wish to have a lifetime shorter than a scope for an object of type T, you can just use std::optional<T>.
 
Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation.

One of the huge benefits to C++ is that destruction happens at well defined times.  This would break that.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

nicolas...@gmail.com

unread,
Aug 24, 2016, 4:25:12 PM8/24/16
to ISO C++ Standard - Future Proposals
On Wednesday, August 24, 2016 at 3:10:04 PM UTC-4, Pablo Oliva wrote:
I don't quite get why simply calling the normal destructor for the object wouldn't achieve the optimization goals you pretend.

It does. But like I wrote before, it's inconvenient, bug prone, and reduces readability. Imagine having to explicitly call a "destructor" on every scalar variable after its last use to make its register available for another variable. We take it for granted that we don't have to do this thanks to liveness analysis. Why accept that we need to do it explicitly for releasing the resources managed by objects?
 
What you seem to be suggesting is that we can signal the complier that an object should be destroyed as soon as possible. This seems to be more of a quality of implementation issue on the compiler's side than something that needs to be standardized. And it doesn't need a different destructor.

Compilers aren't at liberty to call the destructor before the end of the object's scope, unless it has no side effects. So it doesn't work for any object that manages any resources like heap memory. To allow for it while also keeping the legacy behavior we need a new type of destructor.
 
Consider asking your compiler vendor for this as an enhancement, or consider implementing it yourself in an open source compiler. There just doesn't seem to be anything in this (interesting) idea that should be taken care of by the standard's committee, unless I have misunderstood it.
2016-08-24 15:51 GMT-03:00 <nicolas...@gmail.com>:
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?
- Nicolas

--
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.

nicolas...@gmail.com

unread,
Aug 24, 2016, 5:17:26 PM8/24/16
to ISO C++ Standard - Future Proposals, cala...@x.team


On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?

While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.
 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope. So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.
 
For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.

The solution here is to make the lock and the memory that it's guarding part of the same object.
 
Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.

This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.

inkwizyt...@gmail.com

unread,
Aug 24, 2016, 5:31:33 PM8/24/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com


On Wednesday, August 24, 2016 at 10:16:30 PM UTC+2, nicolas...@gmail.com wrote:
On Wednesday, August 24, 2016 at 3:01:34 PM UTC-4, Ville Voutilainen wrote:
On 24 August 2016 at 21:51,  <nicolas...@gmail.com> wrote:
> Hi all,
>
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.

Define "last use".

The last use according to liveness analysis.
 

I have 4 functions:
int main()
{
    resource a
;
    funcA
(&a);
    funcB
();
    funcC
();
    funcD
();
}
What function `funcA` do with `a`? What if it store reference to `a` for future use (or reference to any part of it)? What if `funcB` or `funcD` use this reference too? `a` could be used in other thread (`funcA` === `startThread` and `funcD` === `joinThread`).
Compiler can't know that. Even more, this functions could not exists in exe at all and be from run time dll.


Magnus Fromreide

unread,
Aug 24, 2016, 5:43:42 PM8/24/16
to std-pr...@isocpp.org
This do sound like you are expecting the normal destructor to get called at
end of scope, is this correct?

If so then what would this extra destructor do that

T(std::move(aT));

doesn't do today?

/MF

Matt Calabrese

unread,
Aug 24, 2016, 6:00:56 PM8/24/16
to nicolas...@gmail.com, ISO C++ Standard - Future Proposals
On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.

Again, the problem is that "last use" isn't something that can actually be determined in any meaningful sense. For this to be usable, the *user* needs to notate their last use by explicitly calling some kind of special destructive operation that doesn't currently exist in the language (a hypothetical destructive move operation would be one of those special kinds of operations). I and others have given you examples of why what you want cannot be done truly automatically in a language like C++.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

There is no way in practice to implicitly know when an object is okay to be disposed of without the user providing such information.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope. So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.

Before again going into the subtleties of why annotation strictly at the type level won't work, just think about what you've said -- the user would have to use an equivalent type without eager destruction if they didn't want this behavior. So now you have two types that do exactly the same thing but one with an "eager" destructor? As I'll reiterate, the user effectively needs to somehow notate at the usage point where they are done with the object, but even if you don't get that argument, certainly your desire for two equivalent types should hint to you that someone would at least want to put the notation at the point of declaration of the *object* that they want "eagerly" disposed of, instead of having it be a part of the type. This removes the need for multiple types that you describe.

Anyway, apart from that, another simple example of where this breaks down: you have your matrix type that you've described and some accessor taking a row index and a column index. This function returns a reference to an element of that matrix. Someone uses the function to access an element. When is your matrix ready for disposal? Nobody is 'touching" your matrix type immediately after you call that function, but a reference to one of its elements still exists. Strictly speaking, the matrix could be "eagerly" destroyed as soon as the accessor function returns, before the element is even truly accessed through the reference. Even if you delay your disposal until the end of the full expression, what happens if the reference is stored and accessed in the next statement? This is not at all an unreasonable scenario. Do you expect to track such references? If so, why -- how would the compiler know which references to track. Is the compiler expected to know that the reference which is returned corresponds to something that is owned by the object that was accessed as opposed to just a reference to some other, longer-lived double? What happens if, instead of storing the reference, the result of the access is directly passed along as an argument to another function? Would you just give up on eager destruction there?

Ultimately, the user has the information that is needed and it is trivial for them to specify, just as there is no problem with people writing things like std::move today.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.

The solution here is to make the lock and the memory that it's guarding part of the same object.

This is not always feasible nor does it actually solve the problem.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.

I'd argue that they are very different and it has everything to do with your notion of "last use." This is not something that can actually be detected, and if you rely on a naive notion of "use" being someone directly touching the object, not even through a reference, then IMO it's practically useless and extremely error prone.

I'm sorry if I sound overly negative here, but the problems of this are already well understood. For any kind of early destruction in a language like C++, users would need to be explicit, and that's not bad in this case. I genuinely do believe that something similar to this is worthwhile, but your particular approach is unrealistic (I think specifically that destructive-move would solve a lot of problems with the language and library as it is today).

Nicol Bolas

unread,
Aug 24, 2016, 6:05:38 PM8/24/16
to ISO C++ Standard - Future Proposals, cala...@x.team, nicolas...@gmail.com
On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?

While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.
 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.

The ability to execute the destructor and simultaneously prevent the compiler from doing so is something that can be discussed and debated. But as Matt said, it would be explicit at the point of use, not implicitly built into the type. The code that wants to change the default behavior, which is the code that gains certain responsibilities by doing so, is the code using the object, not the code defining it.

Putting it in the type is simply the wrong place for it. It's the code that needs the optimization who should be responsible for invoking this behavior.

Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope.

No, it gets freed when you want it to be freed. Observe:

auto heap_allocation = std::make_unique<T>(...);

//use the object

heap_allocation
.reset();

See? it's been freed after the last use.

This way, you don't have to play games of wondering exactly when the allocation is destroyed. It's destroyed when you destroy it.

You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:

Object(std::move(auto_val));

That works for pretty much any moveable `Object` type that represents a resource.

The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.
 
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.

You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.

Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.

This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.

What would trigger such a warning? Passing a variable of such a type as a reference to someone else? Because that's all the compiler can know: that a reference or pointer was passed to some function. It has no idea if that function retains that value or not, or when it is expected to relinquish it.

You'd need serious static analysis to avoid a litany of false positives.

Thiago Macieira

unread,
Aug 24, 2016, 6:41:25 PM8/24/16
to std-pr...@isocpp.org, nicolas...@gmail.com
Em quarta-feira, 24 de agosto de 2016, às 13:16:30 PDT,
nicolas...@gmail.com escreveu:
> > Define "last use".
>
> The last use according to liveness analysis.

Let's make it stricter: the last time the object is formally ODR-used.

But then, what happens to lifetime extension via references?

resource a;
const resource &r = a;

When does a get destroyed?

Viacheslav Usov

unread,
Aug 25, 2016, 6:37:41 AM8/25/16
to ISO C++ Standard - Future Proposals
On Wed, Aug 24, 2016 at 8:51 PM, <nicolas...@gmail.com> wrote:

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

"Destruct after last use" is generally known as "garbage collection". There is no known deterministic solution to this general problem. This is what many other comments have been about. You can only make this deterministic if you provide some narrow definition of "last use". C++ has such a narrow definition for local objects, it is the automatic scope rules, simple and hugely successful. The question, then, is whether we really need another competing definition, and whether we really gain much with that additional complexity.

Note I'm not assuming you would propose a non-deterministic definition.

> The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

The assumption that one can decorate a class definition with "destruct after last use" seems overly optimistic to me. Letting the users of the class decorate the object definition as such seems more reasonable.

Cheers,
V.

Nicolas Capens

unread,
Aug 25, 2016, 5:24:39 PM8/25/16
to std-pr...@isocpp.org
Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.

So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:

    class Matrix
    {
    public:
        Matrix(size_t rows, size_t columns);
        auto ~Matrix();   // Eager destructor
        ...
    private:
        double *m;
    };

    {
        Matrix a(10'000, 10'000) = ...;
        Matrix b(10'000, 10'000) = ...;
        Matrix c(10'000, 10'000) = ...;

        a += b;   // Last use of b, destruct before the next line
        a += c;
     }

The alternative of using explicit destruction is really inconvenient:

    {
        Matrix a(10'000, 10'000) = ...;
        Matrix b(10'000, 10'000) = ...;
        Matrix c(10'000, 10'000) = ...;

        a += b;
        b.~Matrix();
        a += c;
    }

As is reducing the scope:

    {
        Matrix a(10'000, 10'000) = ...;

        {
            Matrix b(10'000, 10'000) = ...;
            a += b;
        }

        Matrix c(10'000, 10'000) = ...;  
        a += c;
    }

It might not look that horrible when we're only dealing with one variable that can be destructed eagerly, but it quickly gets unmanageable to manually optimize code like this when using additional variables and longer expressions. So I wish to be able to convey to the compiler that my Matrix class is self-contained and can in have its destructor called before the end of the object's scope.

Obviously it would be a bad idea to allow anyone to retrieve the Matrix::m pointer. If you want to keep access to the object's content, you'd need to keep a reference to the object itself. It would be unreasonable to require the compiler to determine if we're obtaining references to the object's innards and use that to extend its lifetime. But taking a reference to the object itself can easily be determined, just like is done with scalar variables to extend their liveness range.

A second use case I have for this is to reduce the run-time optimization work of an EDSL used for JIT-compiling code: Reactor. The types that are used represent scalar variables (e.g. Int represents int), but have a complex implementation to keep track of the operations performed on them to later be used to generate code from. If the static C++ compiler called their destructor on their last use as the scalar they represent, optimizing the run-time generated code could be quicker.

As for an example of optimizing mutex locking, take a class which takes exclusive ownership of a stream so you can write contiguous data to it:

    {
        Streamer s(stream);
        s.write("200");
        s.write(" OK");   // Last use, call destructor which releases exclusive ownership so another thread can write something.
        do_something_else_before_end_of_scope();
    }

Of course in this trivial case you might just write "200 OK" in one go, but imagine a much lager stream of data that is composed of an unknown number of parts. There would be an overhead from allocating and reallocating storage for the whole. Also, if the reader of the stream can handle partial data then we don't want to delay streaming out the parts.

So I hope this illustrates what I'm after. I'm convinced there's an unambiguous definition of "last use" that can provide us with the desired behavior in all of the above examples, without undue burden on the compiler's analysis. I think liveness analysis when treating the object as a scalar would give us all we need.

One question I have though is whether liveness analysis of scalars is a "solved problem"? In other words are all optimizing compilers capable of producing equivalent, deterministic liveness analysis? If not, I still think a conservative eager destructor would be a great thing to have to optimize the above use cases. I don't think determinism is strictly necessary, since compilers already produce code that can behave differently depending on optimizations.

Thanks again!
- Nicolas

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

btc...@gmail.com

unread,
Aug 25, 2016, 5:35:32 PM8/25/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com
Would it not be convenient enough to define a helper function? Would the following work?

template<class T>
void finish(T&& x)
{
   
auto y{std::move(x)};
}

void f()
{
   
MyExpensiveResource r;
   
/* code */
    finish
(r); // 'r' is not needed anymore.
   
/* code */
}

Nicolas Capens

unread,
Aug 26, 2016, 10:57:36 AM8/26/16
to btc...@gmail.com, ISO C++ Standard - Future Proposals
On Thu, Aug 25, 2016 at 5:35 PM <btc...@gmail.com> wrote:
Would it not be convenient enough to define a helper function? Would the following work?

template<class T>
void finish(T&& x)
{
   
auto y{std::move(x)};
}

void f()
{
   
MyExpensiveResource r;
   
/* code */
    finish
(r); // 'r' is not needed anymore.
   
/* code */
}


No, that's just another way of manually calling r.~MyExpensiveResource(). When you have several of these resources it clutters up your code and complicates making changes. Like I said before, imagine having to do it for all your scalar variables to get good register allocation. That's unacceptable and fortunately modern compilers have liveness analysis so it's not a concern. But we shouldn't have to manually do it for non-basic self-contained types either.

Nicolas Capens

unread,
Aug 26, 2016, 12:35:43 PM8/26/16
to std-pr...@isocpp.org
On Wed, Aug 24, 2016 at 3:21 PM Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, August 24, 2016 at 3:16:52 PM UTC-4, Bernd Lörwald wrote:

> Am 24.08.2016 um 21:01 schrieb Ville Voutilainen <ville.vo...@gmail.com>:
>
>> Limiting the scope with extra braces or by putting the operations in a
>> subroutine also often isn't a desirable workaround. Note that compilers have
>
> ..because..?

It might sometimes be difficult to express the lifetimes as short as possible:

  resource a; // begin minimum lifetime a
  resource b; // begin minimum lifetime b
  last_use (a); // end minimum lifetime a
  long_task (b);
  last_use (b); //end minimum lifetime b
  // end current lifetime b
  // end current lifetime a
 

And how do you know that `b` isn't using `a`?

Thanks for pointing out that caveat. I think the solution here is to simply consider that undefined behavior. That is, when using an "eager" destructor, it is the programmer's responsibility to ensure that the objects behave as a scalar, i.e. a self-contained object. A scalar variable doesn't hold a reference to another variable either (unless it's really just a reference itself, in which case it's easy to keep track of further uses and not call the destructor until the last use of either the explicit reference or the original object).

I'm not entirely sure though what modern compilers do for the above code in the case where resource is a scalar and a is passed to last_use as a reference. We wouldn't know if that reference got stored and then used in the long_task(b) call or last_use(b) call, unless we analyze those functions. Is that common?

That's the point of Ville's "Define 'last use'." question. You cannot just look at code and know that `a` is finished being used when the locally visible code stops using it.

Destruction of automatic objects in the reverse order of their construction is a guarantee that the standard should not arbitrarily discard.

I'm certainly not suggesting to discard it arbitrarily. This feature would have to be opt-in and impose reasonable constraints on how classes with eager destructors can be used, and have some guarantees of what's safe, that can be well understood by developers. I'm still exploring what that means in terms of formal rules, but I'm quite convinced it's possible to improve on the lazy destruction at the end of a scope, for the use cases where this matters.

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Nicolas Capens

unread,
Aug 26, 2016, 12:39:54 PM8/26/16
to std-pr...@isocpp.org
On Wed, Aug 24, 2016 at 3:53 PM Thiago Macieira <thi...@macieira.org> wrote:
On quarta-feira, 24 de agosto de 2016 11:51:01 PDT nicolas...@gmail.com
wrote:
> The use of this would be to free resources held by the object sooner. This
> could be heap memory, file handles, mutex locks, etc. Releasing them as
> soon as we're done with the object that represents them would result in
> more efficient programs.

Locks are the perfect counter-example to what you're proposing. The compiler
simply cannot tell what the "last use" is:

        std::lock_guard lock(mutex);
        i = 1;

Where was the last use: before or after the assignment to i?

That's not a situation where I would use this feature. See the last example here instead. Thank you.
 

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

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Nicolas Capens

unread,
Aug 26, 2016, 1:05:53 PM8/26/16
to std-pr...@isocpp.org
On Wed, Aug 24, 2016 at 4:22 PM Nevin Liber <ne...@eviloverlord.com> wrote:
On 24 August 2016 at 15:16, <nicolas...@gmail.com> wrote:
The code would become littered with braces if you want to limit the scope of each object to its miminal range,

I don't understand why you would want to do that.  Could you elaborate?

Certainly. I wrote down three practical use cases here.
 
And if the objects are truly independent and have no observable effect, can't a compiler do that under the as-if rule?

The problem is that the objects I'm using do have side-effects, so the compiler won't attempt to shorten their lifespan. But in my use cases the side effects are "self-contained", i.e. you could treat them as scalars and destruct them right after their last use.
 
Also, if you wish to have a lifetime shorter than a scope for an object of type T, you can just use std::optional<T>.

How would that work, without requiring manual actions to destruct it?
 
Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation.

One of the huge benefits to C++ is that destruction happens at well defined times.  This would break that.

Yes, in many cases it will be preferable to keep the well defined behavior of implicitly calling the destructor at the end of the variable's scope. The opt-in feature I'm proposing should be used sparingly and cautiously. But when used by a well written framework it should be foolproof for the users of the framework, and offer them significant benefits.
 
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Ren Industries

unread,
Aug 26, 2016, 1:16:27 PM8/26/16
to std-pr...@isocpp.org
Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.

On Fri, Aug 26, 2016 at 1:05 PM, Nicolas Capens <nicolas...@gmail.com> wrote:
On Wed, Aug 24, 2016 at 4:22 PM Nevin Liber <ne...@eviloverlord.com> wrote:
On 24 August 2016 at 15:16, <nicolas...@gmail.com> wrote:
The code would become littered with braces if you want to limit the scope of each object to its miminal range,

I don't understand why you would want to do that.  Could you elaborate?

Certainly. I wrote down three practical use cases here.
 
And if the objects are truly independent and have no observable effect, can't a compiler do that under the as-if rule?

The problem is that the objects I'm using do have side-effects, so the compiler won't attempt to shorten their lifespan. But in my use cases the side effects are "self-contained", i.e. you could treat them as scalars and destruct them right after their last use.
 
Also, if you wish to have a lifetime shorter than a scope for an object of type T, you can just use std::optional<T>.

How would that work, without requiring manual actions to destruct it?
 
Fortunately we don't, because today's compilers do liveness analysis to optimize register allocation.

One of the huge benefits to C++ is that destruction happens at well defined times.  This would break that.

Yes, in many cases it will be preferable to keep the well defined behavior of implicitly calling the destructor at the end of the variable's scope. The opt-in feature I'm proposing should be used sparingly and cautiously. But when used by a well written framework it should be foolproof for the users of the framework, and offer them significant benefits.
 
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.

--
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-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Nicolas Capens

unread,
Aug 26, 2016, 1:51:51 PM8/26/16
to inkwizyt...@gmail.com, ISO C++ Standard - Future Proposals
Yes, that's a very good example of where "last use" is not trivial. But let's again turn toward liveness analysis of scalars. If the compiler can determine that funcA does not store a reference to a, then it can safely free up its register after this call, or, in the case of an object, call its constructor when we indicate that we want this behavior. I assume most compilers would be capable of that? If not, it still seems reasonable to me to spec this eager destruction to be conservative, and in the worst case to result in destruction at the end of the scope.

If funcA is in another module and one of the other functions is also from that same module then the conservative behavior would also be to not call the destructor eagerly. But perhaps we could make an exception here. After all it is very poor API design to hold a reference to a local variable. It could easily have been destroyed just by going out of scope. I don't know of any legitimate uses of that.

Perhaps we can even reasonably make that assumption for functions within the same module. That is, make it undefined behavior or even invalid to store a reference to an eagerly destructed object outside of local variables. It would relieve the compiler from having to inspect functions (if that's an unreasonable thing to do, which I'm not convinced it is).

Nicolas Capens

unread,
Aug 26, 2016, 3:21:42 PM8/26/16
to ISO C++ Standard - Future Proposals, renind...@gmail.com
On Fri, Aug 26, 2016 at 1:16 PM, Ren Industries <renind...@gmail.com> wrote:
Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.

Consider this simple statement:

    x = a * b * c;

Assume they're all scalars, and a, b, and c are no longer used after this statement. That means liveness analysis will make their registers available for other variables, without you having to worry about it. But if instead these variables were huge matrices and you had to manually free their storage to minimize memory consumption, then it would look something like this:

    Matrix t = a * b;
    a.~Matrix();
    b.~Matrix();
    x = t * c;
    t.~Matrix();
    c.~Matrix();

Or if you wanted to do it with scopes:

    {
    Matrix t;
    {
    Matrix a = ...;
    Matrix b = ...;
    t = a * b;
    }
    Matrix c = ...;
    x = t * c;
    }

To me the original statement looks a whole lot easier to read and maintain than the two workarounds. And this is just a trivial example! It would become insane to do it for any realistic data processing code.

Also I think that calling something a "terrible imposition" or not is highly subjective. Many recent C++ features solve issues that already had workarounds which some considered acceptable but were unworkable for others. So please avoid being prejudiced by your own experience and having no direct use for this yourself. C++ supports a huge variety of applications.

I hope the above example illustrates that this is very similar to register allocation, and it's a standards issue that we don't get this behavior today for non-scalar types.
 

Nicolas Capens

unread,
Aug 26, 2016, 3:34:46 PM8/26/16
to ISO C++ Standard - Future Proposals
No, I envisioned that there would just be one implicit call to the destructor, which is either a classic "lazy" one or the newly proposed "eager" one. The only thing that changes is the point where it gets called. So the default is still at the end of the scope, but when opting in to eager destruction it would get called after the last use of the object (with some unresolved wiggle room for the definition of last use).

That said, it's worth entertaining the idea of having both kinds of destructor! Perhaps some objects can have some of their content destructed eagerly while other bits need to stick around until the end of the scope. I can't immediately think of an example, but that doesn't have to stop us from enabling this scenario too. Thanks!
 
If so then what would this extra destructor do that

T(std::move(aT));

doesn't do today?

I need the eager destructor to be called automatically, not manually, to keep the code more readable.
 
/MF


--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposals+unsubscribe@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Nicolas Capens

unread,
Aug 26, 2016, 5:23:53 PM8/26/16
to cala...@x.team, ISO C++ Standard - Future Proposals
On Wed, Aug 24, 2016 at 6:00 PM, Matt Calabrese <cala...@x.team> wrote:
On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote:
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.

Again, the problem is that "last use" isn't something that can actually be determined in any meaningful sense. For this to be usable, the *user* needs to notate their last use by explicitly calling some kind of special destructive operation that doesn't currently exist in the language (a hypothetical destructive move operation would be one of those special kinds of operations). I and others have given you examples of why what you want cannot be done truly automatically in a language like C++.

I'm sorry but that's simply not true. For all of the examples that I provided, it would suffice to call the destructor after the last local use of the variable. The only issue with that is that there's a number of ways in which non-local uses can lead to crashes and whatnot. So it's worth exploring if we can improve on the trivial specification, but if not, it would still be helpful to have.

Let's not forget that destruction at the end of the scope is not foolproof either:

    int *p;
    {
        int i;
        p = &i;
    }
    *p = 0;   // Yikes!

And this can happen in very subtle ways:

    {
        int i;
        foo(i);
    }
    bar();

foo() could be taking i's address and bar could try to dereference it.

All I'm saying is let's not pretend that destruction at end of scope solves the last use issue in the absolute sense. It's a compromise, that happens to be well understood. So we don't have to shoot for the stars for eager destruction either. Destruction at the last local use seems like a workable starting point to me.
 
On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

There is no way in practice to implicitly know when an object is okay to be disposed of without the user providing such information.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope. So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.

Before again going into the subtleties of why annotation strictly at the type level won't work, just think about what you've said -- the user would have to use an equivalent type without eager destruction if they didn't want this behavior. So now you have two types that do exactly the same thing but one with an "eager" destructor?

No, not really. All practical use cases of a Matrix class could use eager destruction. A well written support library for it would avoid storing references that could lead to another use after it was destructed eagerly after the last use in the scope where the object was defined.

Also keep in mind that you can still take explicit control over its lifetime using new/delete. I'm only proposing an alternative implicit lifetime for local variables.

I only mentioned the possibility of having two kinds of classes with different destructor behavior for cases like a mutex lock. Some locks make good use of the destruction at end of scope behavior, while others would benefit from releasing sooner. For well designed classes it should be intuitive. And there shouldn't be two otherwise identical classes with different destructor behavior that you'd use at the same time. Perhaps I shouldn't have used the word "equivalent" before. I merely meant that in the sense of for example a different matrix library which does store references for later use, and so the designer of the library can decide not to use eager destruction.

It doesn't seem useful to me to specify it on a per-object basis. It would be much harder to use than a framework which was designed from the ground up to only use eager destruction on certain types. Not to mention the syntax for this would probably be cumbersome and distracting. Anyway, feel free to give me a counter-example where it's not reasonable to choose between lazy or eager destruction at the type declaration and have a library built for it with that choice in mind.
 
As I'll reiterate, the user effectively needs to somehow notate at the usage point where they are done with the object, but even if you don't get that argument, certainly your desire for two equivalent types should hint to you that someone would at least want to put the notation at the point of declaration of the *object* that they want "eagerly" disposed of, instead of having it be a part of the type. This removes the need for multiple types that you describe.

Anyway, apart from that, another simple example of where this breaks down: you have your matrix type that you've described and some accessor taking a row index and a column index. This function returns a reference to an element of that matrix. Someone uses the function to access an element. When is your matrix ready for disposal? Nobody is 'touching" your matrix type immediately after you call that function, but a reference to one of its elements still exists. Strictly speaking, the matrix could be "eagerly" destroyed as soon as the accessor function returns, before the element is even truly accessed through the reference. Even if you delay your disposal until the end of the full expression, what happens if the reference is stored and accessed in the next statement? This is not at all an unreasonable scenario. Do you expect to track such references? If so, why -- how would the compiler know which references to track. Is the compiler expected to know that the reference which is returned corresponds to something that is owned by the object that was accessed as opposed to just a reference to some other, longer-lived double? What happens if, instead of storing the reference, the result of the access is directly passed along as an argument to another function? Would you just give up on eager destruction there?

This is very similar to the example above with the foo and bar functions. The short answer is: just don't do that. If you want to keep accessing data within your matrix, just access it from the matrix itself again, or make a deep copy. Those are your options. That doesn't seem unreasonable to me.

The idea of merely looking at local uses is starting to grow on me. It solves the use cases that I presented, and is highly deterministic and easy for developers to understand. Basically, when the name of the object is gone, the object is gone. I'm undecided whether local uses of references and pointers to the object itself should extend its lifetime. On the one hand it seems straightforward for the compiler to do so, but on the other hand there's no reason for the developer not to just use the object's name directly.

Ultimately, the user has the information that is needed and it is trivial for them to specify, just as there is no problem with people writing things like std::move today.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
For example, consider one of the kinds of types that you explicitly called out: locks. A lock declaration has no actual knowledge of the memory that it is effectively guarding, so there is no way to *actually* know when the object is truly done with. Indeed, often times the object isn't even touched directly *at all* once constructed. Prematurely destroying such an object would be horrible. This issue is true of basically any object that has a relationship with another object, such as iterators and their associated container.

The solution here is to make the lock and the memory that it's guarding part of the same object.

This is not always feasible nor does it actually solve the problem.

On Wed, Aug 24, 2016 at 2:17 PM, <nicolas...@gmail.com> wrote: 
This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.

I'd argue that they are very different and it has everything to do with your notion of "last use." This is not something that can actually be detected, and if you rely on a naive notion of "use" being someone directly touching the object, not even through a reference, then IMO it's practically useless and extremely error prone.

As far as I can tell it would solve all the examples I had, so it's not useless. And I think you might only be considering it to be extremely error prone because you're not used to it. Note that absolute beginners at C++ always have a hard time understanding the relationship between lifetime and scope. They need to learn the rules of what you can and cannot do. So I think that if you looked at it as if you're learning the language for the fist time, eager destruction wouldn't be any more confusing or error prone. Just stick to a different set of simple rules.
 
I'm sorry if I sound overly negative here, but the problems of this are already well understood. For any kind of early destruction in a language like C++, users would need to be explicit, and that's not bad in this case. I genuinely do believe that something similar to this is worthwhile, but your particular approach is unrealistic (I think specifically that destructive-move would solve a lot of problems with the language and library as it is today).

I'd like to avoid getting sidetracked too much, but remind me again what destructive-move would solve that can't be done by overloading the assignment operator?

Nicolas Capens

unread,
Aug 26, 2016, 5:38:48 PM8/26/16
to Thiago Macieira, ISO C++ Standard - Future Proposals
On Wed, Aug 24, 2016 at 6:41 PM, Thiago Macieira <thi...@macieira.org> wrote:
Em quarta-feira, 24 de agosto de 2016, às 13:16:30 PDT,
nicolas...@gmail.com escreveu:
> > Define "last use".
>
> The last use according to liveness analysis.

Let's make it stricter: the last time the object is formally ODR-used.

ODR as in One Definition Rule? That is, the last time you're using the object's name?

Yes, I'm starting to think that might actually be sufficient, or at least a good starting point.
 
But then, what happens to lifetime extension via references?

        resource a;
        const resource &r = a;

When does a get destroyed?

I'm on the fence about that. One the one hand it seems easy to extend the lifetime to the last use of either a or r. On the other hand, it's really just a useless alias and you could use a instead. Last use of the object's name seems like a very simple deterministic rule that's also easy to comprehend by developers. You wouldn't have to check for references if you wanted to verify your object's lifetimes visually.

The other option is to be conservative. Let the compiler track references, and even check whether functions for which it knows the definition store any of these references for later use. If it can't determine that, give up and destruct at the end of the scope. It would allow for some other usage patterns, but I'm starting to think those might not be of much value and not worth the analysis cost and loss of determinism. I haven't ruled it out yet though... Opinions welcome.

Arthur O'Dwyer

unread,
Aug 26, 2016, 10:39:54 PM8/26/16
to ISO C++ Standard - Future Proposals, renind...@gmail.com, nicolas...@gmail.com
On Friday, August 26, 2016 at 12:21:42 PM UTC-7, Nicolas Capens wrote:
On Fri, Aug 26, 2016 at 1:16 PM, Ren Industries <renind...@gmail.com> wrote:
Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.

Consider this simple statement:

    x = a * b * c;

Assume they're all scalars, and a, b, and c are no longer used after this statement. That means liveness analysis will make their registers available for other variables, without you having to worry about it. But if instead these variables were huge matrices and you had to manually free their storage to minimize memory consumption, then it would look something like this:

    Matrix t = a * b;
    a.~Matrix();
    b.~Matrix();
    x = t * c;
    t.~Matrix();
    c.~Matrix();

Well, it would look like this:

    x = a * b * c;
    a.clear();
    b.clear();
    c.clear();

Or, if you wanted to be more C++11'ish about it,

    x = std::move(a) * std::move(b) * std::move(c);

to indicate that you're really done using those named variables. It's really not a big deal.

I hope the above example illustrates that this is very similar to register allocation, and it's a standards issue that we don't get this behavior today for non-scalar types.

You've already realized that we do get this behavior today for trivially destructible types, and in general we get this behavior for every type by the as-if rule (for types whose destructors have no observable side effects).  Your one remaining concern is that we currently don't get this behavior for destructors whose side effects are observable.

Here we seem to have two cases:
(Case 1) Destructors whose side effects are "important", such as ~std::lock_guard<std::mutex>.
(Case 2) Destructors whose side effects are "unimportant", such as ~std::string.

Let's consider what might happen if we allowed std::string's destructor to run "eagerly."  Some people in this thread have already shown one problem:

    std::string x = "hello";
    char *y = &x[0];
    use(x);
    // run x's destructor now?
    std::cout << y << std::endl;

Here, we are done with the variable x, but we aren't done with the object referenced by x — the object that would be destroyed.
But okay, let's postulate that std::string is a bad candidate for eager destruction because it allows the user to get a pointer into its innards. After all, std::string also unsafely allows us to do things like this —

    std::string x = "hello";
    char *y = &x[0];
    x = "world";
    std::cout << y << std::endl;  // UB

So let's promise not to do anything stupid with pointers, okay? Now are we foolproof?
Well. The side-effect of ~std::string that is observable under the current rules is its memory deallocation, which results in some calls to operator delete[] and so on. What could go wrong if we allow that deallocation to happen eagerly, given that we've promised not to do anything stupid with pointers?

    std::string x = "hello";
    if (true) {
        std::lock_guard<std::mutex> lk(mtx);
        use(x);
        // run x's destructor now?
    }

Here we are performing our last operation on x while holding a mutex lock. Our last operation ("use(x)") is presumably something cheap and fast, or else we would try to do it outside the lock. But the destructor call, by definition, frees memory, which means it's not cheap. (Allocating memory is slower yet, but deallocating memory isn't always super cheap either.) And in fact it probably tries to take a global lock itself, under some circumstances, so now we're holding our original lock on "mtx" through a very slow and possibly even blocking memory-deallocation operation. Well, that's not good!

Okay, so we shouldn't use eager destruction for types that expose references to their internals; and we shouldn't use eager destruction for types whose destructors might block on synchronization or I/O (because those types might get eagerly destructed under a mutex); and it goes without saying that we shouldn't use eager destruction for types whose destructors might throw; and we shouldn't use eager destruction for types whose destructors are trivial (because we get eager destruction for free under the as-if rule in those cases).

So what cases have we got left at this point where eager destruction is possible? Certainly your motivating use-case of "Matrix" isn't a candidate, both because of the internal-reference problem and because of the memory-deallocation-is-a-blocking-operation problem.  My above (Case 1) and (Case 2) turn out to have collapsed into a single case:
(Case 1) Destructors whose side effects might be important to somebody.

my $.02,
–Arthur

Nicol Bolas

unread,
Aug 27, 2016, 11:10:56 AM8/27/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com
On Thursday, August 25, 2016 at 5:24:39 PM UTC-4, Nicolas Capens wrote:
Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.

So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:

I don't think anyone misunderstands what your intended use cases are. But the fact is that things do not get used the way they are intended at all times.

Consider a basic bit of template programming:

T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.

This code, today, works for any type `T` which can be constructed with whatever ... was. Your feature would now make this code suspect. It would only work for `T`s that do not use "eager" destruction. So if someone wanted to instantiate this template with your `Matrix` type, this code breaks.

Normal amounts of compiler analysis cannot fix that. After all, the compiler doesn't (necessarily) know what's going on in `forward_as_tuple`. All the compiler sees is that you're passing a reference in, and you're getting at tuple<T&> out. How could the compiler know that `tpl` contains a reference to `t`? It can't; not without doing serious usage analysis.

And that's for a simple case; `forward_as_tuple` is a template function, so the compiler has its implementation. It could have been a non-template function that wasn't inlined. At which point, it requires whole program analysis to know for certain that the return value contains a reference to a function parameter.

Remember: this is the confounding problem with temporary lifetime extension, when passing temporaries through functions.

Who's fault is this failure? Is it the fault of the person who wrote this template code without knowing that such a feature was going to be introduced? No, of course not. It has to be the fault of the person who passed `Matrix` to this template. But how could they know that `Matrix`'s eager destructor would cause the template to break?

Thus, the blame must fall on the feature itself.

Your post talks about the utility of this feature with "scalar types". But what you're really talking about is how you use the type. The type is safe so long as you treat it as a pure value: you never get a pointer/reference to it or of its contents. But note where this distinction is made. It's not made in the type's declaration (you can get references to `int`, after all). It's made at the point of use.

The problem, as Matt has said, is that your feature works at the type level. To make this feature work, it has to happen at the usage level. The person declaring that variable must explicitly state that they will use the type as a pure value. That they won't get pointers/references to itself or its contents.

The right way to handle this is to make it a function of the object's declaration:

<some_syntax> TypeName varName;

That would cause the compiler to destroy the variable after its last named use.

This allows template programming to actually work. If a template function needed to use eager destruction, then the template would express that in the declaration of an automatic variable. And therefore, the person writing that template function would know that they cannot do tricks on the variable like `forward_as_tuple`.

Also, there still need to be clarifications on what "last use" means. Consider:

Matrix a = ...
for(loop)
{
 
Matrix b = a * whatever;
 
//Do stuff with b, without using a
}

When does `a` get destroyed? I guess it would have to be after the loop, right? Otherwise, the compiler would have to advance the loop prematurely to see if `a` needs to be destroyed.

How does this work with conditional branching? Or God help you, with `goto`?

Nicol Bolas

unread,
Aug 27, 2016, 4:54:47 PM8/27/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com
The other advantage to making eager destruction a function of how the object is declared is that it can be used with anything. We couldn't declare that any currently existing standard library types use eager destruction. And yet, there's no reason why we couldn't use it with most such types, so long as the uses of those objects don't violate the "never get pointers/references" rule.

If eager destruction is as important as you believe it is, then I shouldn't have to write a wrapper type around `vector` or `deque` or whatever just to get that behavior. Users of `vector` have just as much right to benefit from said optimization as users of your class do (so long as they follow the rules).

D. B.

unread,
Aug 27, 2016, 5:03:38 PM8/27/16
to std-pr...@isocpp.org
Nicol: Absolutely. Eager destruction should be a strictly opt-in feature. And it would be better if anyone could opt into it, with any objects they nominate, rather than only those whose interfaces they can edit.

So +1 for the idea of this being an attribute not of a destructor itself, but rather of the declaration - as a hint to the optimiser that it can proceed as-if no one (or no one in immediate scope) has stashed a handle to the instance and use that to deallocate early, iff it sees value.

That said, I wonder how likely this is, given that by my understanding, we're far into GC territory. But as a non-binding hint to the compiler that wouldn't affect semantics of well-formed programs, maybe it's not that outlandish at all... we have several of those already.

Nicol Bolas

unread,
Aug 27, 2016, 6:48:09 PM8/27/16
to ISO C++ Standard - Future Proposals
On Saturday, August 27, 2016 at 5:03:38 PM UTC-4, D. B. wrote:
Nicol: Absolutely. Eager destruction should be a strictly opt-in feature. And it would be better if anyone could opt into it, with any objects they nominate, rather than only those whose interfaces they can edit.

So +1 for the idea of this being an attribute not of a destructor itself, but rather of the declaration - as a hint to the optimiser that it can proceed as-if no one (or no one in immediate scope) has stashed a handle to the instance and use that to deallocate early, iff it sees value.

Hmm. At first, I didn't like the idea of it being merely a hint to the optimizer. When a destructor gets called is an observable side-effect for any code where we would care to use such a thing. So it should be something you could rely on.

After thinking about it for a bit however, making it a hint does solve a number of issues. The most important being the exact definition of "last use". We don't have to have one anymore.

So if you use this attribute on an automatic variable (and having it be a genuine C++ attribute is a legitimate use case), then it says three things.

1. The compiler may call the object's destructor out of the normal order.

2. The object's destructor will only be called after the last time a particular path of execution uses that variable's name.

3. You promise that pointers/references to the object or any object that depends on the destructor not having been executed will not be used after point #2.

We then wouldn't need to define anything beyond that. Compilers would be free to do simple analysis for basic cases, but if there is complex conditional logic that makes it hard to determine where "last use" happens, they are also free to just fall back to the standard behavior.

In this way, it becomes something like named return value elision. A lot of compilers will provide it, so long as your code doesn't get too complex. We know it'll almost certainly work in the simple case of `Type name = ...; /*do stuff*/ return name;`. But if there are multiple return statements and so forth, we become less certain of a compiler's ability to do it.

The same would be true here. The more complex the logic, the less reliable the feature becomes.

That said, I wonder how likely this is, given that by my understanding, we're far into GC territory. But as a non-binding hint to the compiler that wouldn't affect semantics of well-formed programs, maybe it's not that outlandish at all... we have several of those already.

But it would affect the semantics of well-formed programs. See my tuple example above; if `T t` had been annotated with the attribute, then it would make use of `tpl` without the use of `t` undefined behavior. That's part of the promise you make when you use [[eager-destruct]] or whatever we call it.

It's no different from [[noreturn]] in that regard; if such a function ever actually returns, you provoke UB.

D. B.

unread,
Aug 28, 2016, 4:06:15 AM8/28/16
to std-pr...@isocpp.org
On Sat, Aug 27, 2016 at 11:48 PM, Nicol Bolas <jmck...@gmail.com> wrote:

Hmm. At first, I didn't like the idea of it being merely a hint to the optimizer. When a destructor gets called is an observable side-effect for any code where we would care to use such a thing. So it should be something you could rely on.

After thinking about it for a bit however, making it a hint does solve a number of issues.

Heh, I thought I was just inferring what you meant, but instead I came up with a new idea. That was unexpectedly useful! The rest of what you said is a good (probably better than I could've written) outline of what I was thinking. So thanks! Although:

 
That said, I wonder how likely this is, given that by my understanding, we're far into GC territory. But as a non-binding hint to the compiler that wouldn't affect semantics of well-formed programs, maybe it's not that outlandish at all... we have several of those already.

But it would affect the semantics of well-formed programs. See my tuple example above; if `T t` had been annotated with the attribute, then it would make use of `tpl` without the use of `t` undefined behavior. That's part of the promise you make when you use [[eager-destruct]] or whatever we call it.

It's no different from [[noreturn]] in that regard; if such a function ever actually returns, you provoke UB.

Not sure if this was an imprecise choice of words on my part. But what I really meant was precisely what you said:
  • the new behaviour could not suddenly wreck any current, working, compliant programs - since it'd be an opt-in attribute, and
  • if one deliberately added said attribute but also stashed a reference to an object for which it was declared they would not, the program might compile, but it's on the programmer's head if  the optimisation process bites them, as doing so would be UB.


jspha...@gmail.com

unread,
Aug 28, 2016, 5:58:58 AM8/28/16
to std-pr...@isocpp.org


<some_syntax> varName;

what if <some_syntax> is explicitly applied on the intended last use of varName after which any reference to varName will be treated as compile time error

in summary make <some_syntax> end the scope of varName even before the end of the current scope

Get Outlook for Android




--
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.

Nicol Bolas

unread,
Aug 28, 2016, 11:38:39 AM8/28/16
to ISO C++ Standard - Future Proposals
On Sunday, August 28, 2016 at 5:58:58 AM UTC-4, Olanrewaju Adetula wrote:


<some_syntax> varName;

what if <some_syntax> is explicitly applied on the intended last use of varName after which any reference to varName will be treated as compile time error

in summary make <some_syntax> end the scope of varName even before the end of the current scope


The OP explicitly did not want that; he wants the compiler to figure it out for itself, so that he doesn't have to clutter his code up with premature destruction calls.

And there are already other ideas, as Matt suggested, for manually ending the lifetime of an automatic variable.

Matthew Woehlke

unread,
Aug 29, 2016, 11:04:49 AM8/29/16
to std-pr...@isocpp.org
On 2016-08-24 14:51, nicolas...@gmail.com wrote:
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
> [...]

General follow-up... I'm not sure I'm in favor of this proposal in
general, but I don't see it as feasible to make it a type property. You
are almost inevitably going to have cases where the feature needs to be
controlled on a per-variable basis, and having to produce duplicate
types (and convert between them!) is not practical, not to mention that
you may want to use it on types outside your control. (You mention
matrices a lot; well, what if I'm using Eigen matrices and want this?)

And then it occurred to me... we have the `register` keyword just
sitting there. And you keep referring to register allocation of
scalars... it seems that the old meaning of `register` is almost exactly
what you want.

It may also make sense to make it UB to take and retain a reference to
any `register` variable, including references to members of the same. If
we *really* wanted to be safe, we could make `register` a qualifier
(i.e. allow its use on function parameters) and make it *ill-formed* to
take a (non-`register`) reference to a `register` variable. This way,
you could still pass such a variable by reference to a function, but the
function would not be allowed to retain that reference. (This obviously
implies that no global variable may be `register` qualified, and the
presence of a `register` qualified member implies that the containing
object may only be used as a `register` variable itself.) This would
require much more extensive API modification for the feature to be
widely used, but the added safety might be worth it.

In fact, I can see some other potential (though probably breaking) uses
for such a feature. For example, std::string::data() could return `char*
register` instead of plain `char*` to indicate to callers that they may
not retain the returned pointer. (Maybe this could be a feature for STL2...)

--
Matthew

Matthew Woehlke

unread,
Aug 29, 2016, 11:11:29 AM8/29/16
to std-pr...@isocpp.org, cala...@x.team
On 2016-08-26 17:23, Nicolas Capens wrote:
> On Wed, Aug 24, 2016 at 6:00 PM, Matt Calabrese wrote:
>> I'm sorry if I sound overly negative here, but the problems of this are
>> already well understood. For any kind of early destruction in a language
>> like C++, users would need to be explicit, and that's not bad in this case.
>> I genuinely do believe that something similar to this is worthwhile, but
>> your particular approach is unrealistic (I think specifically that
>> destructive-move would solve a lot of problems with the language and
>> library as it is today).
>
> I'd like to avoid getting sidetracked too much, but remind me again what
> destructive-move would solve that can't be done by overloading the
> assignment operator?

Destructive move a.k.a. relocation solves the problem of having to
maintain invariants in an object that is about to be destroyed anyway.
Consider for example your large matrix, with the invariant that its
storage is always allocated. In order to move construct an instance, you
must first allocate memory, then exchange the new memory with the
instance being moved from, so that the moved-from instance remains in a
valid state.

With relocation, you *know* that the old instance is being destroyed
anyway, so you can just take possession of its memory instead without
any new allocations.

--
Matthew

Nicolas Capens

unread,
Aug 29, 2016, 11:22:42 AM8/29/16
to Nicol Bolas, ISO C++ Standard - Future Proposals, Matt Calabrese
On Wed, Aug 24, 2016 at 6:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?

While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.
 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.

It's not a word game. Premature anything is a bad thing and we shouldn't present this feature to users that way. Lazy vs. eager is more neutral. Other suggestions welcome.

Anyway, making the destruction happen in a single, well-defined place can be done by using the last "local" use of the object. It's also the most bug prone, but if determinism is a necessity, then this is a feasible starting point. It's not clear to me though that determinism is necessary. We don't ask compilers to free up registers in a determinable way either. Anywhere between the last conservative use and the end of the scope is valid. For my use cases of eager destruction that's acceptable as well. I'd just like make sure that compilers aren't going to be overly conservative.
 
The ability to execute the destructor and simultaneously prevent the compiler from doing so is something that can be discussed and debated. But as Matt said, it would be explicit at the point of use, not implicitly built into the type. The code that wants to change the default behavior, which is the code that gains certain responsibilities by doing so, is the code using the object, not the code defining it.

First of all, that doesn't solve my use cases. And secondly I can already do that today using a method to free up the resources held by the object and being careful that the destructor doesn't attempt to free them again. If there's still value in your feature that I might be missing, fine, feel free to discuss it elsewhere.
 
Putting it in the type is simply the wrong place for it. It's the code that needs the optimization who should be responsible for invoking this behavior.

Anyway, for the use cases that I envision, the annotation at destructor declaration is appropriate. For example say I need a huge matrix to temporarily store some intermediate results. I could use "double matrix[10000][10000];" and rely on the compiler's optimizations to use the memory for other purposes as soon as I'm done with this matrix. But it's simply not going to fit on the stack. So instead I'll use an object with a pointer to heap memory. But now it only gets freed when it goes out of scope.

No, it gets freed when you want it to be freed. Observe:

auto heap_allocation = std::make_unique<T>(...);

//use the object

heap_allocation
.reset();

See? it's been freed after the last use.

This way, you don't have to play games of wondering exactly when the allocation is destroyed. It's destroyed when you destroy it.

I don't want to manually destroy it. That's a critical part of the feature I'm proposing. It needs to call the destructor automatically, just like today for local objects, but call it more eagerly than at the end of the scope so we get some optimization benefits without programmer involvement. The only room for debate is whether we aggressively call the destructor at the last local use, or require it to be conservative, or something in between.
 
You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:

Object(std::move(auto_val));

That works for pretty much any moveable `Object` type that represents a resource.

The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.

Why? We don't question whether a register is still in use or not, don't we? There are some objects, some, not all of them, that could be treated to be self-contained just like a register so that the compiler is free to optimize their lifetime to something shorter than their scope.
 
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.

You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.

The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.
 
Even with that, you might think that it's okay to annotate types that contain no logical relationships to objects that it does not own. For instance, something that just contains an int or a unique_ptr to some object with no relationships, etc. The problem is, even in these cases, there is nothing stopping some *other* object from storing a pointer or reference to an instance of your automatically-prematurely-destructible-type. If someone *does* hold such a reference and your annotated type is "no longer used", then the object that refers to it will now have a dangling reference. Point being, your annotation cannot be at the type level.

This issue is not very different from still having a reference to a local variable after it has gone out of scope. C++ is inherently unsafe, and you simply need to know what you're doing. With lazy destruction, don't use any references to the object after it has gone out of scope. With eager destruction, don't use any references to the object after its last use. A compiler warning could help prevent people from shooting themselves in the foot in both cases.

What would trigger such a warning? Passing a variable of such a type as a reference to someone else? Because that's all the compiler can know: that a reference or pointer was passed to some function. It has no idea if that function retains that value or not, or when it is expected to relinquish it.

You'd need serious static analysis to avoid a litany of false positives.

Indeed. This is the kind of information that will determine whether we can go with the conservative option or need the more aggressive one. But I'm not fully convinced that this analysis is hard to do. The equivalent problem for registers is called interprocedural register allocation, and I was able to find research papers on it dating back to the early 80's. And knowing whether a function retained a reference is in fact easier than knowing which registers it uses.

Nicolas Capens

unread,
Aug 29, 2016, 1:02:42 PM8/29/16
to Ren Industries, ISO C++ Standard - Future Proposals
On Fri, Aug 26, 2016 at 5:46 PM Ren Industries <renind...@gmail.com> wrote:
That in no way demonstrates it is similar to register allocation, which is, again, a QOI issue.

My apologies. The example omitted how the equivalent scalar code would have its registers freed up for use by other variables after their last use. I thought that would be sufficiently clear. Here's an illustration of that: http://compilers.cs.ucla.edu/fernando/projects/puzzles/tutorial/. The end of the live ranges corresponds to where I'd like the destructors of objects to be called. Let me know if the similarities are still not obvious to you and I'll create a more detailed side-by-side example if I find the time.

Anyway, yes, register allocation is a quality of implementation issue. One that is enabled by the standard allowing for it because scalars having no side-effects on destruction. I want to enable that same freedom of optimization for objects that do have side-effects, by telling the compiler that we don't care about these side-effects to happen at the end of the variable's scope.
 
It is not necessarily the case that a compiler need do that, and I can think of at least a few counter examples. For example, there are projects that output C++ to javascript or java, which have no notion of register allocation as you point to it. These "machines" are perfectly valid C++ targets, yet have absolutely no notion of what you are talking about.

I realize that. But I shouldn't have to care if a compiler strictly needs to do that today or not. I'm talking about adding a new feature to the language, and this may involve adding some new implementation requirements. If that causes some cross-compilers to no longer be able to support this new version of C++ without some non-trivial effort, too bad. It's certainly not my aim to make implementer's lives difficult, but I also don't want to cripple or abandon the feature because of it.

Indeed, C++ supports a huge variety of applications. More than you know of, it seems.

You're talking about Emscripten and alike. That's implementation side. I was talking about applications written in C++. You seemed to see no practical uses for this feature, presumably because you hadn't encountered such an application before. Did my trivial example clarify that manual freeing of the resources is not acceptable, or do you still disagree?
 
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

--
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.

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Nicol Bolas

unread,
Aug 29, 2016, 2:46:37 PM8/29/16
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, cala...@x.team, nicolas...@gmail.com
On Monday, August 29, 2016 at 11:22:42 AM UTC-4, Nicolas Capens wrote:
On Wed, Aug 24, 2016 at 6:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?

While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.
 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.

It's not a word game. Premature anything is a bad thing and we shouldn't present this feature to users that way. Lazy vs. eager is more neutral. Other suggestions welcome.

The term "playing word games" refers to making an argument about the name of something in order to make it seem more palatable. So you seem to be admitting to playing word games while simultaneously denying it.

You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:

Object(std::move(auto_val));

That works for pretty much any moveable `Object` type that represents a resource.

The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.

Why? We don't question whether a register is still in use or not, don't we?

I don't know why you keep bringing up registers. Object lifetimes are not like registers in any way, shape, or form. Registers are an implementation detail of a particular machine/compiler. Object lifetimes are specified by the standard.

This is a false analogy; please stop using it.

So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.

You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.

The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.

Your interface cannot stop people from using the object "incorrectly". Consider the template example I used elsewhere:


T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.

This is perfectly valid and reasonable code; there is no reason to consider this code to not be "under normal circumstances" for C++. Template code like this already exists, and it will work with any type `T` which can be constructed.

There is nothing you as the creator of the type `T` to make it so that `T`'s interface will prevent someone from doing this.

And there are no tricks the compiler can play to be absolutely certain that `tpl` doesn't contain a reference to `t`. Yes, as I admitted elsewhere, `forward_as_tuple` is inline, so the compiler could in theory figure it out in that case. But it cannot do so in the general case. To the compiler, a non-inline function is a complete black box; it knows the inputs and it knows the outputs. But it doesn't know what happens in the middle.

This problem is fundamentally identical to the temporary lifetime extension problem. Namely, that this works:

const auto &str = std::string(...);
//use str

While this does not:

const auto &str = std::string(...).c_str();
//use str

Why doesn't this work? Because the compiler cannot know that `c_str` returns a pointer/reference into something controlled by the temporary. Therefore, the compiler must assume that the function's return value is independent of the lifetime of the parameter/this. This could have worked if `c_str` were a data member. But because it's a member function, it will not extend the lifetime of the temporary.

Your problem is the exact same thing, just for named variables instead of temporaries. The compiler does not and cannot know, in all cases, whether a return value of a function contains a pointer/reference to an argument or an element of an argument.

Your framework cannot prevent "incorrect" use of the type. So your proposal is flawed.

Nicol Bolas

unread,
Aug 29, 2016, 2:52:57 PM8/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com


On Monday, August 29, 2016 at 11:04:49 AM UTC-4, Matthew Woehlke wrote:
On 2016-08-24 14:51, nicolas...@gmail.com wrote:
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
> [...]

General follow-up... I'm not sure I'm in favor of this proposal in
general, but I don't see it as feasible to make it a type property. You
are almost inevitably going to have cases where the feature needs to be
controlled on a per-variable basis, and having to produce duplicate
types (and convert between them!) is not practical, not to mention that
you may want to use it on types outside your control. (You mention
matrices a lot; well, what if I'm using Eigen matrices and want this?)

And then it occurred to me... we have the `register` keyword just
sitting there. And you keep referring to register allocation of
scalars... it seems that the old meaning of `register` is almost exactly
what you want.

It may also make sense to make it UB to take and retain a reference to
any `register` variable, including references to members of the same. If
we *really* wanted to be safe, we could make `register` a qualifier
(i.e. allow its use on function parameters) and make it *ill-formed* to
take a (non-`register`) reference to a `register` variable.

So now you want `const virtual register` qualifiers on variables? Do we really need that?

The attribute approach seems so much cleaner.

Nicolas Capens

unread,
Aug 29, 2016, 2:55:07 PM8/29/16
to Arthur O'Dwyer, ISO C++ Standard - Future Proposals, renind...@gmail.com
On Fri, Aug 26, 2016 at 10:39 PM Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Friday, August 26, 2016 at 12:21:42 PM UTC-7, Nicolas Capens wrote:
On Fri, Aug 26, 2016 at 1:16 PM, Ren Industries <renind...@gmail.com> wrote:
Why is it such a terrible imposition to require manual interaction to enforce this behavior? I don't buy your argument that it is similar to register allocation; that's a QOI not a standards issue.

Consider this simple statement:

    x = a * b * c;

Assume they're all scalars, and a, b, and c are no longer used after this statement. That means liveness analysis will make their registers available for other variables, without you having to worry about it. But if instead these variables were huge matrices and you had to manually free their storage to minimize memory consumption, then it would look something like this:

    Matrix t = a * b;
    a.~Matrix();
    b.~Matrix();
    x = t * c;
    t.~Matrix();
    c.~Matrix();

Well, it would look like this:

    x = a * b * c;
    a.clear();
    b.clear();
    c.clear();

Or, if you wanted to be more C++11'ish about it,

    x = std::move(a) * std::move(b) * std::move(c);

to indicate that you're really done using those named variables. It's really not a big deal.

It is a big deal. That was just a trivial example, but it already makes things significantly less elegant. Now imagine manually indicating the end of the live range of every Reactor variable in this code: https://swiftshader.googlesource.com/SwiftShader/+/master/src/Shader/SetupRoutine.cpp Also note that you'd have to update it for every change to the actual algorithms, and you'd need perfect test coverage of every path. This is also one of the shorter Reactor functions in that project.

I'd like to separate the algorithms that I'm trying to express, from the optimizations that are performed on it. Currently those optimizations have to be done at run-time. With eager destruction some of it would be done at static compile time. And that's just one of the three use cases I envision for this. I'm sure there's more. So please don't discard this as not a big deal. One way to understand its value even more is by imagining we've had it for a long time and then it gets taken away. The closest thing to that would be to not have liveness analysis for register allocation and thus required manually indicating the end of the lifetime of each scalar variable. That's clearly unacceptable. Yet that's what we're dealing with today for objects. I think we can come up with a feature to improve on that.
 
I hope the above example illustrates that this is very similar to register allocation, and it's a standards issue that we don't get this behavior today for non-scalar types.

You've already realized that we do get this behavior today for trivially destructible types, and in general we get this behavior for every type by the as-if rule (for types whose destructors have no observable side effects).  Your one remaining concern is that we currently don't get this behavior for destructors whose side effects are observable.

Correct.
 
Here we seem to have two cases:
(Case 1) Destructors whose side effects are "important", such as ~std::lock_guard<std::mutex>.
(Case 2) Destructors whose side effects are "unimportant", such as ~std::string.

Exactly. Let's optimize case 2. Note though that we wouldn't want to do it for std::string itself to avoid breaking legacy content. It can be used for new classes which have clearly opted into the eager destruction behavior and for which the correct usage is intuitive. I'm being intentionally vague about what that means exactly. I just don't want there to be any confusion that I'm not proposing to alter any legacy behavior.
 
Let's consider what might happen if we allowed std::string's destructor to run "eagerly."  Some people in this thread have already shown one problem:

    std::string x = "hello";
    char *y = &x[0];
    use(x);
    // run x's destructor now?
    std::cout << y << std::endl;

Here, we are done with the variable x, but we aren't done with the object referenced by x — the object that would be destroyed.

Yes, this is a problem, but let's not get deterred by that. Note that it's also possible to still have a reference to an object that got destructed at the end of its scope. That's a problem too. It could have been partially mitigated by not destroying objects until the end of the function. Some languages go even farther by using garbage collection. So it's all just part of a spectrum of lifetime management solutions. Each has its advantages and disadvantages, and I'm just proposing a new one with its own advantages but also some additional disadvantages.

So with that in mind, my suggestion is that it should be invalid to keep using a reference to the internal storage after the last explicit use of the object. Note that for this particular example all you have to do is use x again, instead of y. For self-contained objects that would be the norm anyway. A string class with fewer ways to access its contents would help enforce that.
 
But okay, let's postulate that std::string is a bad candidate for eager destruction because it allows the user to get a pointer into its innards. After all, std::string also unsafely allows us to do things like this —

    std::string x = "hello";
    char *y = &x[0];
    x = "world";
    std::cout << y << std::endl;  // UB

So let's promise not to do anything stupid with pointers, okay? Now are we foolproof?
Well. The side-effect of ~std::string that is observable under the current rules is its memory deallocation, which results in some calls to operator delete[] and so on. What could go wrong if we allow that deallocation to happen eagerly, given that we've promised not to do anything stupid with pointers?

    std::string x = "hello";
    if (true) {
        std::lock_guard<std::mutex> lk(mtx);
        use(x);
        // run x's destructor now?
    }

Here we are performing our last operation on x while holding a mutex lock. Our last operation ("use(x)") is presumably something cheap and fast, or else we would try to do it outside the lock. But the destructor call, by definition, frees memory, which means it's not cheap. (Allocating memory is slower yet, but deallocating memory isn't always super cheap either.) And in fact it probably tries to take a global lock itself, under some circumstances, so now we're holding our original lock on "mtx" through a very slow and possibly even blocking memory-deallocation operation. Well, that's not good!

This is another carefully constructed example of a problem for which an equivalent problem exists with destruction at end of scope (by moving x's definition into the conditional block). Granted, it's more obvious that way that the object will get destructed while the lock is held, but I doubt a lot of people would even be conscious about it before it shows up in a profiler. And when you're really serious about performance you're going to make sure that eager destruction is helping you instead of slowing things down. It's kind of in the name itself. Eager or lazy strategies are not inherently better one versus the other. It depends on the situation. Likewise lazy destruction can be the cause of many performance issues.

I'll put this down as another example why eager destruction should be used with care. But it doesn't convince me at all that it wouldn't be a valuable feature.
 
Okay, so we shouldn't use eager destruction for types that expose references to their internals; and we shouldn't use eager destruction for types whose destructors might block on synchronization or I/O (because those types might get eagerly destructed under a mutex); and it goes without saying that we shouldn't use eager destruction for types whose destructors might throw; and we shouldn't use eager destruction for types whose destructors are trivial (because we get eager destruction for free under the as-if rule in those cases).

So what cases have we got left at this point where eager destruction is possible? Certainly your motivating use-case of "Matrix" isn't a candidate, both because of the internal-reference problem and because of the memory-deallocation-is-a-blocking-operation problem.  My above (Case 1) and (Case 2) turn out to have collapsed into a single case:
(Case 1) Destructors whose side effects might be important to somebody.

I think you're dismissing it too easily because of the blocking operation problem. That's really just a rare situation. By a similar argument we could dismiss lazy destruction as not viable. That's clearly not very sensible. Again, it's all just part of a spectrum of lifetime management solutions and we have to value each for their advantages and cope with their disadvantages.

So I'd rather focus our effort on determining whether eager destructions should be done aggressively or conservatively. The most aggressive option is to call the destructor after the last local use of the object's name. It has the nice property of being fully deterministic and easy to understand by humans, but also has a considerable risk of not being used correctly and causing issues. Slightly less aggressive is to also require the compiler to track local references as an alias for the object. It doesn't have many practical uses but eliminates at least one case that would cause issues, at virtually no cost. More conservative solutions would allow/require the compiler to track non-local references as well, but would require dropping determinism guarantees or imposing demands on static analysis.

I don't have strong arguments yet for or against these different options.
 
my $.02,
–Arthur

Matthew Woehlke

unread,
Aug 29, 2016, 3:05:59 PM8/29/16
to std-pr...@isocpp.org
On 2016-08-29 14:46, Nicol Bolas wrote:
> This problem is fundamentally identical to the temporary lifetime extension
> problem. Namely, that this works:
>
> const auto &str = std::string(...);
> //use str
>
> While this does not:
>
> const auto &str = std::string(...).c_str();
> //use str

I think having explicit temporary types might help here:

std::string::c_str() -> char const* register; // declaration

// error: taking reference of register-qualified type.
const auto &str = std::string(...).c_str();

Obviously, it is too late to change std::string this way, but it would
help us write better API's going forward. Specifically:

> The compiler does not and *cannot* know, in all cases, whether a
> return value of a function contains a pointer/reference to an
> argument or an element of an argument.

...it would be a way to solve this.

On 2016-08-29 14:52, Nicol Bolas wrote:
> So now you want `const virtual register` qualifiers on variables? Do we
> *really* need that?

Well... *yes*. For exactly the reason given above. Making it a qualifier
makes it possible to actually *solve* some of these problems, at least
by making them explicitly ill-formed rather than just subtly broken.

Mind, this idea isn't fully baked, but it might (*might*) have potential...

--
Matthew

Matthew Woehlke

unread,
Aug 29, 2016, 3:27:31 PM8/29/16
to std-pr...@isocpp.org
On 2016-08-29 15:05, Matthew Woehlke wrote:
> On 2016-08-29 14:46, Nicol Bolas wrote:
>> This problem is fundamentally identical to the temporary lifetime extension
>> problem. Namely, that this works:
>>
>> const auto &str = std::string(...);
>> //use str
>>
>> While this does not:
>>
>> const auto &str = std::string(...).c_str();
>> //use str
>
> I think having explicit temporary types might help here:
>
> std::string::c_str() -> char const* register; // declaration
>
> // error: taking reference of register-qualified type.
> const auto &str = std::string(...).c_str();
>
> Obviously, it is too late to change std::string this way, but it would
> help us write better API's going forward. Specifically:
>
>> The compiler does not and *cannot* know, in all cases, whether a
>> return value of a function contains a pointer/reference to an
>> argument or an element of an argument.
>
> ...it would be a way to solve this.

To expand on that: a `register` qualified return type is an explicit
temporary which *may not* be lifetime extended. For example:

foo(char const* register sp);
bar(char const*);

std::string s{"abcd"};
// s::c_str() -> char const* register
// s::operator[](size_t) -> char& register

// okay; return value of c_str is "destroyed" after the call to foo()
foo(s.c_str());

// okay; return value of operator[] is "destroyed" after assignment
s[0] = 'x';

// error: would lifetime-extend a `register` type
auto p = s.c_str();
auto& c = s[0];

// error: no matching function
bar(s.c_str());

A `register` qualified function parameter may not be stored in non-local
storage. (Basically, a parameter that is register qualified is a promise
- an *enforced* promise - that the callee will not retain a reference to
that parameter or any of its innards.)

There problems to be worked out, and some may be insurmountable. It's a
very preliminary idea, more in the 'is this interesting enough to be
worth pursuing?' stage. (In particular, non-pointer/reference-types need
to be copyable to non-register-qualified types, and *possibly* to
const-qualified references.)

The biggest problem, though, is that using the qualifier would be
extremely pervasive, i.e. you have to use it end to end in your entire
API or you run into all sorts of problems. For example, copying a
register-qualified type would need a copy ctor that takes a
register-qualified parameter...

--
Matthew

Nicol Bolas

unread,
Aug 29, 2016, 4:38:08 PM8/29/16
to ISO C++ Standard - Future Proposals, mwoehlk...@gmail.com
On Monday, August 29, 2016 at 3:05:59 PM UTC-4, Matthew Woehlke wrote:
On 2016-08-29 14:46, Nicol Bolas wrote:
> This problem is fundamentally identical to the temporary lifetime extension
> problem. Namely, that this works:
>
> const auto &str = std::string(...);
> //use str
>
> While this does not:
>
> const auto &str = std::string(...).c_str();
> //use str

I think having explicit temporary types might help here:

  std::string::c_str() -> char const* register; // declaration

  // error: taking reference of register-qualified type.
  const auto &str = std::string(...).c_str();

Obviously, it is too late to change std::string this way, but it would
help us write better API's going forward.

How would that solve the problem? How does the compiler connect the return value's lifetime to the lifetime of one of the function parameters? Which parameter should it connect it to? All of them?

And why would you use `register` of all things for that?
 
Specifically:

> The compiler does not and *cannot* know, in all cases, whether a
> return value of a function contains a pointer/reference to an
> argument or an element of an argument.

...it would be a way to solve this.

On 2016-08-29 14:52, Nicol Bolas wrote:
> So now you want `const virtual register` qualifiers on variables? Do we
> *really* need that?

Well... *yes*. For exactly the reason given above. Making it a qualifier
makes it possible to actually *solve* some of these problems, at least
by making them explicitly ill-formed rather than just subtly broken.

P0066 provided a more comprehensive solution to lifetime analysis. And it was ultimately rejected.

Nicolas Capens

unread,
Aug 29, 2016, 5:21:28 PM8/29/16
to Nicol Bolas, ISO C++ Standard - Future Proposals
On Sat, Aug 27, 2016 at 11:10 AM Nicol Bolas <jmck...@gmail.com> wrote:
On Thursday, August 25, 2016 at 5:24:39 PM UTC-4, Nicolas Capens wrote:
Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.

So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:

I don't think anyone misunderstands what your intended use cases are. But the fact is that things do not get used the way they are intended at all times.

Consider a basic bit of template programming:

T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.

This code, today, works for any type `T` which can be constructed with whatever ... was. Your feature would now make this code suspect. It would only work for `T`s that do not use "eager" destruction. So if someone wanted to instantiate this template with your `Matrix` type, this code breaks.

There are already many situations in which seemingly well understood code can behave differently than expected. Copy elision, exceptions, platform-specific typedefs, operator overloading, and virtual functions are all known to catch people by surprise. It's an inherent property of C++ that many different things can happen behind our backs, which can also be incredibly powerful. So I think eager destruction might require tossing some old assumptions out of the window, but it will enable some valuable new optimizations.

Normal amounts of compiler analysis cannot fix that. After all, the compiler doesn't (necessarily) know what's going on in `forward_as_tuple`. All the compiler sees is that you're passing a reference in, and you're getting at tuple<T&> out. How could the compiler know that `tpl` contains a reference to `t`? It can't; not without doing serious usage analysis.

And that's for a simple case; `forward_as_tuple` is a template function, so the compiler has its implementation. It could have been a non-template function that wasn't inlined. At which point, it requires whole program analysis to know for certain that the return value contains a reference to a function parameter.

I'm not entirely convinced yet that this is unreasonable to be expected from a modern compiler. Destruction at the end of the scope, interprocedural register allocation, and garbage collection all have existed for decades. Isn't it time to add a new option which might require some static analysis? If not now, can we have it the next century? Either way I feel like we're stuck while technically we're already able to do better. We just have to be willing to make the necessary changes to the standard, and figure out the right balance between determinism, ease of use, conservatism, etc.

By the way, one other potential way this could be solved is to not allow storing referenced to classes with eager destruction. That is, they can be function arguments, but they can't be member variables or global variables. The above code would then simply not compile and you'd get a clear error message about it.
 
Remember: this is the confounding problem with temporary lifetime extension, when passing temporaries through functions.

Who's fault is this failure? Is it the fault of the person who wrote this template code without knowing that such a feature was going to be introduced? No, of course not. It has to be the fault of the person who passed `Matrix` to this template. But how could they know that `Matrix`'s eager destructor would cause the template to break?

Thus, the blame must fall on the feature itself.

Your post talks about the utility of this feature with "scalar types". But what you're really talking about is how you use the type. The type is safe so long as you treat it as a pure value: you never get a pointer/reference to it or of its contents. But note where this distinction is made. It's not made in the type's declaration (you can get references to `int`, after all). It's made at the point of use.

Taking reference to int is handled by liveness analysis by treating it as an alias. And when it disappears into an unknown function it can fall back to keeping the variable live until the end of its scope. But in many cases it can still optimize register usage, and a crucial part of that is knowing that there are no noticeable side effects. That's a property of its type. Likewise I think that for cases where an object is self-contained and we don't care when the side effects of its destruction happen, that too should be a property of the type.

Note that I'm not married to this idea. I'm just trying to make sure you can see things my way as well before we discuss the pros and cons of the alternatives.
 
The problem, as Matt has said, is that your feature works at the type level. To make this feature work, it has to happen at the usage level. The person declaring that variable must explicitly state that they will use the type as a pure value. That they won't get pointers/references to itself or its contents.

The right way to handle this is to make it a function of the object's declaration:

<some_syntax> TypeName varName;

If this can be part of a typedef (like const), then that seems reasonable to me. Note that I really wouldn't want to annotate the use of a Reactor type at each variable declaration.

I just think it would end up being used as part of a typedef, all the time. For "legitimate" cases where you need to indicate it on a per declaration level, it seems like it would be a badly designed class and framework, and using it correctly would be much more painful than using a class which always uses eager destruction and helps you avoid the pitfalls by design. Also such code would be hard to refactor. It seems mentally less demanding to me to just know that for certain types you always have to avoid certain scenarios.

That would cause the compiler to destroy the variable after its last named use.

This allows template programming to actually work. If a template function needed to use eager destruction, then the template would express that in the declaration of an automatic variable. And therefore, the person writing that template function would know that they cannot do tricks on the variable like `forward_as_tuple`.

Also, there still need to be clarifications on what "last use" means. Consider:

Matrix a = ...
for(loop)
{
 
Matrix b = a * whatever;
 
//Do stuff with b, without using a
}

When does `a` get destroyed? I guess it would have to be after the loop, right? Otherwise, the compiler would have to advance the loop prematurely to see if `a` needs to be destroyed.

How does this work with conditional branching? Or God help you, with `goto`?

Good points. For the Streamer use case it would make sense to be aggressive about calling the destructor at the earliest possible, while for the Matrix case it might be reasonable to wait until the next branch merge point. This isn't our biggest point of contention right now, but we'll need to get back to this. Thanks for bringing it up.

Peter Koch Larsen

unread,
Aug 29, 2016, 6:07:14 PM8/29/16
to std-pr...@isocpp.org
I just had a glance at the code in your link. A ~200 line function
seemingly in dire need of refactoring.
Such code is not a good example to argue about liveness of variables.
You should refactor the code
into smaller, logical elements and demonstrate that "eager
destruction" is still needed.

/Peter

Nicolas Capens

unread,
Aug 29, 2016, 11:07:09 PM8/29/16
to Nicol Bolas, ISO C++ Standard - Future Proposals
On Sat, Aug 27, 2016 at 4:54 PM Nicol Bolas <jmck...@gmail.com> wrote:
The other advantage to making eager destruction a function of how the object is declared is that it can be used with anything. We couldn't declare that any currently existing standard library types use eager destruction. And yet, there's no reason why we couldn't use it with most such types, so long as the uses of those objects don't violate the "never get pointers/references" rule.

If eager destruction is as important as you believe it is, then I shouldn't have to write a wrapper type around `vector` or `deque` or whatever just to get that behavior. Users of `vector` have just as much right to benefit from said optimization as users of your class do (so long as they follow the rules).

That's indeed an attractive argument. I'm still a bit wary though that using a new feature like eager destruction with a legacy class could more easily lead to bugs. For example std::string too easily allows access to its internal storage. Also, if eager destructions isn't fully deterministic but implementation dependent, then something that might work fine on one compiler can cause issues on another. This is somewhat less likely when a class was designed with eager destruction in mind in the first place.

Anyway, I'm not against providing the user with more options. Perhaps we can simply allow to specify eager destruction both at the class level and per declaration.

Nicolas Capens

unread,
Aug 29, 2016, 11:57:46 PM8/29/16
to std-pr...@isocpp.org
On Mon, Aug 29, 2016 at 11:04 AM Matthew Woehlke <mwoehlk...@gmail.com> wrote:
On 2016-08-24 14:51, nicolas...@gmail.com wrote:
> I'd like to propose a new kind of destructor that gets called after a local
> object's last use, instead of at the end of its scope.
> [...]

General follow-up... I'm not sure I'm in favor of this proposal in
general, but I don't see it as feasible to make it a type property. You
are almost inevitably going to have cases where the feature needs to be
controlled on a per-variable basis, and having to produce duplicate
types (and convert between them!) is not practical, not to mention that
you may want to use it on types outside your control. (You mention
matrices a lot; well, what if I'm using Eigen matrices and want this?)

Although you're not alone in the opinion that this would have to be controlled on a per-variable basis, I still haven't seen any sound arguments for that. It seems too mix-and-matchy to me, putting a significant burden on both the writer and the reader of the code to make sure it's working as intended. It seems very likely to me that a portion of code is going to use a set of variables of the same type that can be eagerly destructed and which avoids the pitfalls, or you don't really care about it for that and just want the legacy lazy destruction and the assurance that you can take references to internal data the usual way.

Types are either designed to behave self-contained or not. So it makes more sense to me to link eager destruction to the type.

I do understand the desire to use eager destruction on types outside of your control, but let's be careful not to prioritize short term benefits over long term issues. All classes that would benefit from eager destruction, eventually will use it, and this might involve some careful redesigning by the authors. Forcefully overriding that may cause significant problems in the longer term.
 
And then it occurred to me... we have the `register` keyword just
sitting there. And you keep referring to register allocation of
scalars... it seems that the old meaning of `register` is almost exactly
what you want.

That's indeed a pretty good option for a recycled keyword for this. :-) It didn't quite have the same meaning, but does convey the idea of self-containedness or wanting a variable to be more aggressively optimized. It was also always merely a hint, and in legacy code it's mostly used on scalars so there is little risk of breaking it.
 
It may also make sense to make it UB to take and retain a reference to
any `register` variable, including references to members of the same. If
we *really* wanted to be safe, we could make `register` a qualifier
(i.e. allow its use on function parameters) and make it *ill-formed* to
take a (non-`register`) reference to a `register` variable. This way,
you could still pass such a variable by reference to a function, but the
function would not be allowed to retain that reference. (This obviously
implies that no global variable may be `register` qualified, and the
presence of a `register` qualified member implies that the containing
object may only be used as a `register` variable itself.) This would
require much more extensive API modification for the feature to be
widely used, but the added safety might be worth it.

Yes, I think this is a really good idea that would avoid many if not all of the pitfalls with taking references to eagerly destructed types/variables!
 
In fact, I can see some other potential (though probably breaking) uses
for such a feature. For example, std::string::data() could return `char*
register` instead of plain `char*` to indicate to callers that they may
not retain the returned pointer. (Maybe this could be a feature for STL2...)

--
Matthew

--
You received this message because you are subscribed to a topic in the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this topic, visit https://groups.google.com/a/isocpp.org/d/topic/std-proposals/IUxA4cx8yHc/unsubscribe.
To unsubscribe from this group and all its topics, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.

Nicol Bolas

unread,
Aug 30, 2016, 12:55:36 AM8/30/16
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, nicolas...@gmail.com
On Monday, August 29, 2016 at 11:07:09 PM UTC-4, Nicolas Capens wrote:
On Sat, Aug 27, 2016 at 4:54 PM Nicol Bolas <jmck...@gmail.com> wrote:
The other advantage to making eager destruction a function of how the object is declared is that it can be used with anything. We couldn't declare that any currently existing standard library types use eager destruction. And yet, there's no reason why we couldn't use it with most such types, so long as the uses of those objects don't violate the "never get pointers/references" rule.

If eager destruction is as important as you believe it is, then I shouldn't have to write a wrapper type around `vector` or `deque` or whatever just to get that behavior. Users of `vector` have just as much right to benefit from said optimization as users of your class do (so long as they follow the rules).

That's indeed an attractive argument. I'm still a bit wary though that using a new feature like eager destruction with a legacy class could more easily lead to bugs. For example std::string too easily allows access to its internal storage.

Which is perfectly valid, so long as you use the object correctly:

[[eager-destruct]] std::string str = std::to_string(53.4f);
std
::cout << str;
//done with `str`.

Correct behavior in accord with eager destruction is defined by how you use the object, not how the type is written.

And because its defined at the point of use, the person using the feature likely knows what its rules are and knows how to abide by them.

Also, if eager destructions isn't fully deterministic but implementation dependent, then something that might work fine on one compiler can cause issues on another.

And how's that different from any other form of undefined behavior?

The rule is very simple: it is UB to do anything which requires that an automatic variable's destructor has not been called after the last use of that variable's name in the control flow of that variable's scope.

Also, you have yet to prove that you can define "last use" in a "fully deterministic" way. Which is why the attribute approach is better; it guarantees nothing. It allows compilers to provide quick destruction in simple cases, but it doesn't force them into doing crazy analysis of conditional logic. They can if they want, but that's all QOI.

This is somewhat less likely when a class was designed with eager destruction in mind in the first place.

Anyway, I'm not against providing the user with more options. Perhaps we can simply allow to specify eager destruction both at the class level and per declaration.

I don't know if you're understanding why putting it on the type is a bad thing. The reasoning has been scattered across this thread, so let me try to collate it here.

To put this property on the type itself forces every user of that type to abide by certain rules. The type, from the perspective of the user, has transparently changed the rules of object lifetime, relative to any other type.

The compiler cannot verify that you are following those rules (see below). If you break the rules, the compiler cannot detect it in every case. Therefore, it falls on the people using that type to abide by those rules. This places a burden on the user of the type. They have to know that it is a type with wonky lifetime rules and make sure to abide by its rules.

Furthermore, template code already exists which does not abide by these rules. And there is no way for them to have been written to detect a property that did not know existed when they were written. Users must therefore know the implementation details of any template code that they use this type with, avoiding instantiating a template with one of these "eager" objects lest the template code break them.

All of this creates a general atmosphere of fragility in your programming environment. You have to abide by arcane rules, which the compiler cannot detect violations of, just to use some type as an automatic variable. These are rules that you did not ask for. That you did not agree to use.

And it is all done for an alleged performance gain that may not matter to you or your program (premature optimization is premature).

By putting it on the object declaration, we give people great power. They can choose to use it where they want. But when they exercise that great power, they are also willingly choosing to assume the great responsibilities that come with it.

And the people who have absolutely no need for it and don't care, they can use standard object lifetime rules. They don't need the burden of those responsibilities, so they don't use the feature. Who are you to say that they should have to?

Below

No amount of static analysis can pierce a DLL/SO boundary. Code that isn't even available to the linker cannot possibly be analyzed at compile time. Static analysis is also confounded by non-static constructs, like function pointers and virtual functions. Static analysis after all is static. Programs are not static constructs.

So even in the best possible case, where the compiler/linker has access to all of the code, it is impossible to know exactly how every function will use a type. It cannot know whether a return value references a parameter or not.

Not in the general case. And if it can't do it in the general case, then we cannot require that it be done.

Nicol Bolas

unread,
Aug 30, 2016, 1:17:52 AM8/30/16
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, nicolas...@gmail.com
On Monday, August 29, 2016 at 5:21:28 PM UTC-4, Nicolas Capens wrote:
On Sat, Aug 27, 2016 at 11:10 AM Nicol Bolas <jmck...@gmail.com> wrote:
By the way, one other potential way this could be solved is to not allow storing referenced to classes with eager destruction.
That is, they can be function arguments, but they can't be member variables or global variables. The above code would then simply not compile and you'd get a clear error message about it.

You wouldn't necessarily get such an error. Why? Because `std::tuple` doesn't have to store the object as a true member variable. It can be implemented by creating an internal buffer big enough to hold all of the objects, then use placement new to initialize them. And thus, it is a de-facto member without being a member subobject.

Also, the tuple above stores a reference, not a value. Which also defies static analysis, because the tuple may not be storing it as a true member subobject. Are you going to forbid using placement new as well?

One more thing. Let's pretend that forbidding the use of these types as subobjects, globals, or placement `new` would solve the problem. Why is this a good thing? I don't know of a class type where it is reasonable to forbid objects of that type from being a member subobject of another class.

When you're having to add unpleasant features to types just to make your idea functional, that is generally a sign that it's not the right feature.

The problem, as Matt has said, is that your feature works at the type level. To make this feature work, it has to happen at the usage level. The person declaring that variable must explicitly state that they will use the type as a pure value. That they won't get pointers/references to itself or its contents.

The right way to handle this is to make it a function of the object's declaration:

<some_syntax> TypeName varName;

If this can be part of a typedef (like const), then that seems reasonable to me.

It would be an attribute on the automatic variable declaration, not of a type specifier.

Note that I really wouldn't want to annotate the use of a Reactor type at each variable declaration.

Why would I want or need to? Precisely how much performance improvement would eager destruction get me in terms of building Reactor functions? Why is eager destruction of paramount optimization concern with Reactor usage?

I just think it would end up being used as part of a typedef, all the time. For "legitimate" cases where you need to indicate it on a per declaration level, it seems like it would be a badly designed class and framework, and using it correctly would be much more painful than using a class which always uses eager destruction and helps you avoid the pitfalls by design.

As proven elsewhere, you cannot avoid the pitfalls of such types through interface alone. At best, you make it slightly less fragile. But all of the template cases still fail, since they won't necessarily care about your interface.

Also such code would be hard to refactor.

Why? How does this attribute change refactoring at all?

Using this attribute is an optimization tool. Optimization happens after refactoring. If you're about to refactor some code, you would generally rip out all of these attributes first.

It seems mentally less demanding to me to just know that for certain types you always have to avoid certain scenarios.

That would cause the compiler to destroy the variable after its last named use.

This allows template programming to actually work. If a template function needed to use eager destruction, then the template would express that in the declaration of an automatic variable. And therefore, the person writing that template function would know that they cannot do tricks on the variable like `forward_as_tuple`.

Also, there still need to be clarifications on what "last use" means. Consider:

Matrix a = ...
for(loop)
{
 
Matrix b = a * whatever;
 
//Do stuff with b, without using a
}

When does `a` get destroyed? I guess it would have to be after the loop, right? Otherwise, the compiler would have to advance the loop prematurely to see if `a` needs to be destroyed.

How does this work with conditional branching? Or God help you, with `goto`?

Good points. For the Streamer use case it would make sense to be aggressive about calling the destructor at the earliest possible, while for the Matrix case it might be reasonable to wait until the next branch merge point.

... huh? I'm talking about flow control analysis; the nature of the type is completely irrelevant. If you can determine what "the earliest possible" point is with `Streamer`, then you can determine it with `Matrix` too.
 

Andrzej Krzemieński

unread,
Aug 30, 2016, 5:49:58 AM8/30/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com


W dniu czwartek, 25 sierpnia 2016 23:24:39 UTC+2 użytkownik Nicolas Capens napisał:
Thanks all for the initial feedback! It's clear to me now that I haven't defined "last use" very well, and giving it an accurate definition based on my limited knowledge of compiler lingo is going to be tricky.

So let me take a step back and describe what real-world issues I'm trying to solve. The first example is one where I'd like the heap memory managed by an object to get freed at the same point where a scalar variable's register would be made available for a different variable:

    class Matrix
    {
    public:
        Matrix(size_t rows, size_t columns);
        auto ~Matrix();   // Eager destructor
        ...
    private:
        double *m;
    };

    {
        Matrix a(10'000, 10'000) = ...;
        Matrix b(10'000, 10'000) = ...;
        Matrix c(10'000, 10'000) = ...;

        a += b;   // Last use of b, destruct before the next line
        a += c;
     }

The alternative of using explicit destruction is really inconvenient:

    {
        Matrix a(10'000, 10'000) = ...;
        Matrix b(10'000, 10'000) = ...;
        Matrix c(10'000, 10'000) = ...;

        a += b;
        b.~Matrix();
        a += c;
    }

As is reducing the scope:

    {
        Matrix a(10'000, 10'000) = ...;

        {
            Matrix b(10'000, 10'000) = ...;
            a += b;
        }

        Matrix c(10'000, 10'000) = ...;  
        a += c;
    }


Note that to some extent you can fix this problem with std::optional (in C++17, now in a TS):

 
{
 
Matrix a(10'000, 10'000) = ...;
  optional
<Matrix> b(in_place, 10'000, 10'000) = ...;
 
Matrix c(10'000, 10'000) = ...;

  a
+= *b;
  b
= nullopt;
  a
+= c;
}

Regards,
&rzej;
Message has been deleted

Edward Catmur

unread,
Aug 30, 2016, 7:07:40 AM8/30/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com

(Reposting from correct account; sorry for the noise.)


You can do better than that using the exchange-empty idiom:

 

a += *std::exchange(b, nullopt);


However I believe that OP considers this also too clumsy (look for discussion of std::move above).

 

One thing I haven't seen mentioned yet in this discussion is that allocation and deallocation is already elidable per [expr.new]/10. This means that e.g. the destructor of std::string is not an observable operation.


FrankHB1989

unread,
Aug 30, 2016, 8:38:03 AM8/30/16
to ISO C++ Standard - Future Proposals, nicolas...@gmail.com
It seems that some kind of strong liveness analysis (as so-called static garbage collection) is need here, to infer the lifetime during translation. Since it can be deterministic, it may be interestingly suitable for C++. However, I don't like the idea of multiple "destructors"; too messy, e.g. so called "destructor"/finalizer/IDisposable in some current languages.

And besides the "destructor" issue, I doubt the problem is over-simplified too aggressively. Do you really want to create some kind of affine type systems rather than extends the current one? (BTW, I don't like the proposed syntactic notations. There may be just better a new keyword unless you can wrapped the features in a library form.)

Nicolas Capens

unread,
Aug 30, 2016, 10:12:20 AM8/30/16
to Ren Industries, ISO C++ Standard - Future Proposals
On Mon, Aug 29, 2016 at 1:34 PM Ren Industries <renind...@gmail.com> wrote:
I 100% disagree that manual freeing is not acceptable; as Arthur indicated, it is not particularly hard to work around.
I agree with him; it is not a big deal.

There are also several people who seem to agree with me that eager destruction is a feature worth exploring. Either way, while your opinion has been duly noted despite any lack of clear counterarguments, if you don't have anything constructive to add then I question why you're even participating in the discussion. It's totally fine if you don't see any future for this feature. It's just futile to try to convince people otherwise who do, no matter how much "percent" you disagree.
 
Furthermore, it's absolutely absurd you'd say "too bad" to working and quite important projects that do in fact use C++; that is not "implementation side"; it is a perfectly valid backend that does not in any way do "register allocation". 

We can picture machines that do not use registers; why exactly should we add an implementation detail to C++, which is defined in terms of an abstract machine NOT a real one?

First of all please note that my "too bad" was both conditional and not intentional. I was just conveying an unfortunate fact about some new language features. So don't shoot the messenger.

Secondly, C++ has "register" as a keyword. Sure, it's a deprecated one, but it would be disingenuous to deny its history. Cross-compilation wasn't a design consideration back then, and I doubt it's still anything more than an afterthought. That doesn't mean I don't have sympathy for it. I happen to have experience in GLSL to HLSL graphics shader translation, which is crucial to all major web browsers on Windows. It's really annoying when the target language doesn't have a direct equivalent of each feature. But I can't blame spec authors from wanting to move ahead with additional features.

Likewise Emscripten downright doesn't support certain features or supports them poorly. It's unreasonable to expect the C++ standard to hold back on adding any more features that would be hard or impossible to support.

I do not doubt the practical use for such a feature; in fact, I could use such a feature quite often, and manually annotate my code to make such features possible, even going so far as to occasionally use wrappers to make it easier. Just because I can see a feature is useful does not mean it deserves to be part of the standard, or that we should disrupt currently working code to implement it, especially when a library solution is so obvious.

It's always going to be a matter of opinion whether the gains outweigh the costs. But first of all we need to maximize the gains and minimize the costs. We're still in the relatively early phases of that exploitative work, with currently some new ideas being passed around that could make it a lot more attractive than what you might have initially envisioned. Secondly, C++ has never really been a language that shies away from features that may only have select use cases or are not foolproof. So even if you don't think it "deserves" it, it might still be embraced by the standard.

I'm a curious about your "obvious" library solution though, although I fear it would just be another undesirable manual method.
 
I understand what you mean by register allocation, but I again do not find it to be in any way similar beyond superficially, and once again, register allocation is part of implementation not the standard. It is completely possible for a compiler to do no register allocation at all; the reason it can is because of the "as if" rule. You want non-trivial destructors to be called out of order? Not allowed by the "as if" rule, for very good reasons. Trying to hack it in using implementation details is a bad idea.

Repeating that register allocation is part of the implementation is not going to deter me in any way. It's one of the options for supporting eager destruction. And while register allocation is not a literal part of the standard, I'm going to repeat once more that it's only due to the standard's specification that compilers are enabled to do it. Similarly we need a change of the standard to accommodate eager destruction of objects. And that's true regardless of whether we decide to make it merely a hint and dependent on the implementation, or more deterministic. It looks like you have a preference toward the latter. Perhaps you'd like to elaborate on that with some constructive arguments.
 
Guess that's "too bad" for you, then.

I will gladly accept any outcome of this discussion.

Nicolas Capens

unread,
Aug 30, 2016, 11:20:54 AM8/30/16
to Nicol Bolas, ISO C++ Standard - Future Proposals, cala...@x.team
On Mon, Aug 29, 2016 at 2:46 PM Nicol Bolas <jmck...@gmail.com> wrote:
On Monday, August 29, 2016 at 11:22:42 AM UTC-4, Nicolas Capens wrote:
On Wed, Aug 24, 2016 at 6:05 PM, Nicol Bolas <jmck...@gmail.com> wrote:
On Wednesday, August 24, 2016 at 5:17:26 PM UTC-4, nicolas...@gmail.com wrote:
On Wednesday, August 24, 2016 at 3:19:24 PM UTC-4, Matt Calabrese wrote:
On Wed, Aug 24, 2016 at 11:51 AM, <nicolas...@gmail.com> wrote:
Hi all,

I'd like to propose a new kind of destructor that gets called after a local object's last use, instead of at the end of its scope.

The use of this would be to free resources held by the object sooner. This could be heap memory, file handles, mutex locks, etc. Releasing them as soon as we're done with the object that represents them would result in more efficient programs.

Currently such optimization can only be achieved by explicitly releasing the resources, which is inconvenient, bug prone, and reduces readability. Limiting the scope with extra braces or by putting the operations in a subroutine also often isn't a desirable workaround. Note that compilers have been doing liveness analysis for register allocation and stack compaction for a very long time and we take these optimizations for granted. I'd like to see it get extended to heap memory and other resources as well.

The syntax for this could be an annotation of the destructor declaration with a keyword, symbol, or attribute. For example, "auto ~Object();", "volatile ~Object();", "~~Object();", or "[[eager]] ~Object();".

Thoughts?

While I would like C++ to eventually get a way to do the equivalent of this in some form (more generally, I want destructive move, which is related to this even though it may not be immediately obvious),

Yes, a destructive assignment could count as a last use of its previous value and call the destructor that I'm proposing. I'd also like it to be called after the last use as a source argument too though.
 
I do not think that the appropriate place to notate the premature destruction is on the destructor declaration. Instead, I believe it needs to be explicit at the usage-site. This is primarily because we are in a language where side-effects matter.

I wouldn't call it premature destruction. Eager instead of today's lazy destruction at the end of the scope, seems like a better description to me.

Playing word games doesn't change the fact that the standard makes it clear that destruction of an automatic object happens in a single, well-defined place. Your proposal wants to *transparently* change that location based on something in the type of that object.

It's not a word game. Premature anything is a bad thing and we shouldn't present this feature to users that way. Lazy vs. eager is more neutral. Other suggestions welcome.

The term "playing word games" refers to making an argument about the name of something in order to make it seem more palatable. So you seem to be admitting to playing word games while simultaneously denying it.

Except I didn't argue about using a name that could make it seem more palatable. Like I said, with lazy vs. eager one isn't inherently better than the other. But premature is always a bad thing. So insisting on using that would be a pejorative word game. And again, I'm open to a better name. Just don't use one that would cause a negative prejudice because that could damage having a constructive discussion.
 
You can explicitly reset smart pointers; you can explicitly close streams. And for moveable types that don't offer such features, Magnus gave a great answer:

Object(std::move(auto_val));

That works for pretty much any moveable `Object` type that represents a resource.

The point in time when an automatic variable is destroyed should not be a mystery. It should never be in question when an automatic variable is no longer a legitimate object.

Why? We don't question whether a register is still in use or not, don't we?

I don't know why you keep bringing up registers. Object lifetimes are not like registers in any way, shape, or form. Registers are an implementation detail of a particular machine/compiler. Object lifetimes are specified by the standard.

This is a false analogy; please stop using it.

It's my proposal. Making object lifetimes somewhat analogous to register lifetimes is what I'm after. You're very welcome to have your own proposal. Just don't conflate it with mine and dictate what I can or cannot want.

With that out of the way, note that it's only one of the two options. Either we use eager destruction as a hint, so it does become an implementation detail. You seem to be liking that idea. But that goes against your "the point in time when an automatic variable is destroyed should not be a mystery" mantra. Either way perhaps the analogy of register allocation isn't so crazy after all?

The second option is to have a deterministic "last use" within the local scope, potentially with restrictions on storing references to avoid incorrect usage. Admittedly this option would be less analogous to register allocation.
 
So I want to make it clear to the compiler that I'd like it to be destroyed eagerly, and not just for this one but for every instance. It's a property of the class itself that I want it's destructor to be called eagerly. If I don't want that behavior I can simply use an equivalent class without eager destructor.

You've provided a very good reason not to do this at all. We do not want to encourage the proliferation of types that differ only by the presence or absence of "eager destruction". We don't want `eager_unique_ptr` or `eager_ifstream` or `eager_vector` or whatever.

The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.

Your interface cannot stop people from using the object "incorrectly". Consider the template example I used elsewhere:


T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.

This is perfectly valid and reasonable code; there is no reason to consider this code to not be "under normal circumstances" for C++. Template code like this already exists, and it will work with any type `T` which can be constructed.

There is nothing you as the creator of the type `T` to make it so that `T`'s interface will prevent someone from doing this.

First of all, there's no law that dictates that legacy code under new conditions still has to have the same semantics. Something as simple as a + b would for a C developer be nothing other than an addition of scalars, while in C++ it can mean absolutely anything due to operator overloading. That can be badly abused, but it can also be a very powerful abstraction. So I don't think not breaking your template example is a strict necessity.

Secondly, we could explore whether we can make it a compilation error to use this code with an eagerly destructed type/variable.
 
And there are no tricks the compiler can play to be absolutely certain that `tpl` doesn't contain a reference to `t`. Yes, as I admitted elsewhere, `forward_as_tuple` is inline, so the compiler could in theory figure it out in that case. But it cannot do so in the general case. To the compiler, a non-inline function is a complete black box; it knows the inputs and it knows the outputs. But it doesn't know what happens in the middle.

This problem is fundamentally identical to the temporary lifetime extension problem. Namely, that this works:

const auto &str = std::string(...);
//use str

While this does not:

const auto &str = std::string(...).c_str();
//use str

Why doesn't this work? Because the compiler cannot know that `c_str` returns a pointer/reference into something controlled by the temporary. Therefore, the compiler must assume that the function's return value is independent of the lifetime of the parameter/this. This could have worked if `c_str` were a data member. But because it's a member function, it will not extend the lifetime of the temporary.

Your problem is the exact same thing, just for named variables instead of temporaries. The compiler does not and cannot know, in all cases, whether a return value of a function contains a pointer/reference to an argument or an element of an argument.

I beg to differ. Compilers can know the dependency caused by this reference, through static analysis. And they can be conservative about it if insufficient information is not available. C++ just chose not to support temporary lifetime extension. But we shouldn't have to restrict ourselves to such an aggressive approach for future features. Eager destruction can either also be aggressive and not support the use of any references after the last local use (without or without compiler warning/error), or we can make it conservative and merely a hint so that it's up to the compiler implementation how eagerly the destructor gets called.
 
Your framework cannot prevent "incorrect" use of the type. So your proposal is flawed.

Many C++ features can be used incorrectly with nothing to prevent you from it. That hasn't stopped them from making it into the standard. That said, if this is really a major concern of eager destruction, then we should explore how we can make some or all of the incorrect uses a compiler error, or, it means we have to go with the conservative option. 

Patrice Roy

unread,
Aug 30, 2016, 8:53:36 PM8/30/16
to std-pr...@isocpp.org
It's an interesting optimization problem, I think. My first reflex reading all this (apart from just writing smaller functions and make the problem disappear, but I know people don't always do that) was to do manual move-from or apply some other technique (maybe a std:: function like std::last_use(T && arg) that essentially does auto whatever = std::move(arg); along with some manoeuver to avoid unused variable warnings) and suggest std::last_use() users to think about move semantics a lot.

The OP's opinion that manual markup is as tedious as manual scoping (or refactoring) and that static analysis should take over is interesting, but not something I'm confident enough to mandate on compiler writers (unless they, themselves, say they can do it). On the other hand, there might be compilers that can confidently do it, in some cases at least. Thus, should the OP want to go ahead, my feeling (and I'm not a compiler writer, so they can correct me all they like and they'll be right :) ) would be to go with an attribute on variable / argument declaration in this case. It would allow markup of individual variables, and would make it Ok to be taken as an optimistic suggestion from the perspective of compilers that can't really do it, or in the cases where they cannot determine the point of last use clearly from the source code.

I'm guessing users of this attribute would mark costly variables on which they'd like eager destruction only, and would accept the longer compilation times associated with the liveliness analysis involved. I'm also guessing that people writing smaller functions would essentially not need this, but that it could be useful for those who prefer not to refactor their code (or don't know how to, or just would prefer to think of other things as they see programming as a means to an end, or...). Should a compiler not be able to perform the task suggested by this attribute, the «manual» option remains open.

It's what comes to my mind right now. I don't think anything stronger that this could be standardized anyway (it delves into issues like dynamic linking and others where the standard doesn't really tread all that much :) ). Hope it's useful, half-baked as it is.

--
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-proposals+unsubscribe@isocpp.org.

To post to this group, send email to std-pr...@isocpp.org.

Matthew Woehlke

unread,
Aug 31, 2016, 11:05:49 AM8/31/16
to std-pr...@isocpp.org
On 2016-08-29 16:38, Nicol Bolas wrote:
> On Monday, August 29, 2016 at 3:05:59 PM UTC-4, Matthew Woehlke wrote:
>> On 2016-08-29 14:46, Nicol Bolas wrote:
>>> This problem is fundamentally identical to the temporary lifetime
>>> extension problem. Namely, that this works:
>>>
>>> const auto &str = std::string(...);
>>> //use str
>>>
>>> While this does not:
>>>
>>> const auto &str = std::string(...).c_str();
>>> //use str
>>
>> I think having explicit temporary types might help here:
>>
>> std::string::c_str() -> char const* register; // declaration
>>
>> // error: taking reference of register-qualified type.
>> const auto &str = std::string(...).c_str();
>>
>> Obviously, it is too late to change std::string this way, but it would
>> help us write better API's going forward.
>
> How would that solve the problem? How does the compiler connect the return
> value's lifetime to the lifetime of one of the function parameters?* Which*
> parameter should it connect it to? All of them?

...yes? I'm not sure you're asking the right question; `register` here
means that the value becomes invalid "almost immediately", i.e. as soon
as the inputs that led to its "creation" change, the return value is
invalidated. (So, yes, I want to say "all of them" is the correct answer.)

> And why would you use `register` of all things for that?

It grew out of the original idea of `register` being used to annotate
something that is "more ephemeral than usual". (And also, because it's
available.) Nothing says we couldn't use something else.

> P0066 provided a more comprehensive solution to lifetime analysis. And it
> was ultimately rejected
> <https://botondballo.wordpress.com/2015/11/09/trip-report-c-standards-meeting-in-kona-october-2015/>
> .

Ah, well, that's not reassuring. Oh, well. I'm not sure I agree that the
problem is 'too niche', but...

--
Matthew

Nicol Bolas

unread,
Aug 31, 2016, 1:45:32 PM8/31/16
to ISO C++ Standard - Future Proposals, jmck...@gmail.com, cala...@x.team, nicolas...@gmail.com
On Tuesday, August 30, 2016 at 11:20:54 AM UTC-4, Nicolas Capens wrote:
On Mon, Aug 29, 2016 at 2:46 PM Nicol Bolas <jmck...@gmail.com> wrote:
With that out of the way, note that it's only one of the two options. Either we use eager destruction as a hint, so it does become an implementation detail. You seem to be liking that idea. But that goes against your "the point in time when an automatic variable is destroyed should not be a mystery" mantra.

There's a difference between "liking" an idea and "thinking that it isn't flawed". My feelings on the attribute approach are the latter. That is, if a feature like this is to happen, one that causes the early destruction of an object based on the last use of its name, without care to the existence of references and the like, then annotation at the point of definition for that object is the approach that avoids flaws the most. It's the approach which breaks no existing code, provides the desired functionality, and makes sure that the responsibilities required by the optimization are shouldered by the people who actually want it.

That doesn't mean I like the idea. Personally, I think broad use of this feature, in any form, will lead to code that is needlessly difficult to reason about. I think it would be better for people who have a genuine need to clean up objects early to simply clean up objects early via existing tools (or destructive move, should that ever show up). This way, compilers don't have to implement name-usage-analysis through complex branching code to figure out when you "last used" a name. Because destruction is done explicitly, it makes your code easier to reason about on inspection, since everyone can clearly see exactly when an object has been destroyed (code is read more often than it is written).

The localized attribute form is merely the least bad of the alternatives. Don't mistake that for me thinking that it's a good idea.
 
The way I envision this to be used you won't need such a prefix at all. It would just be Matrix, Int, or Streamer, like in my examples. The framework they're part of is responsible of making sure they're self-contained objects and under normal circumstances can't be used incorrectly.

Your interface cannot stop people from using the object "incorrectly". Consider the template example I used elsewhere:


T t = ...
auto tpl = forward_as_tuple(t);
//do something with tpl.

This is perfectly valid and reasonable code; there is no reason to consider this code to not be "under normal circumstances" for C++. Template code like this already exists, and it will work with any type `T` which can be constructed.

There is nothing you as the creator of the type `T` to make it so that `T`'s interface will prevent someone from doing this.

First of all, there's no law that dictates that legacy code under new conditions still has to have the same semantics. Something as simple as a + b would for a C developer be nothing other than an addition of scalars, while in C++ it can mean absolutely anything due to operator overloading. That can be badly abused, but it can also be a very powerful abstraction. So I don't think not breaking your template example is a strict necessity.

Operator overloading works by taking syntax that would have been illegal and making it legal. Without operator overloading, applying + to class types would be a syntax error.

So you're making a false equivalence here. Your feature is exactly the opposite of operator overloading. OO takes illegal syntax and makes it potentially legal, while yours takes meaningful, functioning syntax and makes it potentially illegal.

Silently illegal.

We don't add features to C++ that break existing code like this: transparently and silently. We can do that, but only if the benefits of the feature outweigh the downsides. I fail to see how the benefits of putting the declaration in the type outweigh the downsides here.

Secondly, we could explore whether we can make it a compilation error to use this code with an eagerly destructed type/variable.

I'm curious as to exactly how that would work. There are infinitely many permutations of this code. The only commonalities between them are:

1: The creation of an object of that type on the stack.

2: The passing of a pointer/reference of that type to a function that returns a value.

3: The returned value happening to include a pointer/reference to that object or some object that it controls.

It is impossible to know that #3 happens, given just a function declaration. And when dealing with any dynamic code (virtual functions, function pointers, `any`, `function`, DLL boundaries, etc), a function declaration is all you have. So how would you presume to error on trying to compile this?

You can't eliminate #2; calling a member function passes a pointer to that object to the member function. You can't even play games about there being a pointer/reference in the return value of the object's type, since it's possible to fake that in ways compilers cannot detect. See the tuple example, where if the implementation uses fake-subobjects, the compiler can't tell that the tuple contains a reference.

So you can't detect #3 and you can't eliminate #2. How exactly would you make this situation a compile error?

Your framework cannot prevent "incorrect" use of the type. So your proposal is flawed.

Many C++ features can be used incorrectly with nothing to prevent you from it. That hasn't stopped them from making it into the standard.

Sure. But nobody would claim that C++'s temporary lifetime extension rules aren't flawed.

It's one thing to have something already in the standard, then realizing that it has significant flaws that are difficult to correct. It's quite another to put something into the standard when you know it is a flawed idea.
Reply all
Reply to author
Forward
0 new messages