Date: Sat, 23 May 2026 07:36:52 +0000
On Thursday, May 21, 2026, Jonathan Wakely wrote:
>
>
> Yeah, I don't want spooky changes to the lifetime model and how it
> interacts with scopes to happen implicitly based on something far away on
> the function declaration. If somebody adds __persistent to a declaration,
> it silently changes the meaning of existing callers. No thanks.
>
What if we were to move the 'mark' to the calling site. Perhaps a unary
operator? So the following:
int main(void)
{
%async(Func);
}
would be the same as:
int main(void)
{
auto dummy = async(Func);
}
Of course this begs the question, what about loops?
for ( unsigned i = 0u; i < N; ++i ) %async(Func);
Well I think in the above line, the scope of the return value should be to
the nearest preceding {, meaning that after the loop has finished, you have
N objects on the stack as the N threads run concurrently (implemented by
the compiler by calling 'alloca' inside the loop with linked list).
But what if the loop has a curly-braced body?
for ( unsigned i = 0u; i < N, ++i )
{
%async(Func);
Log("new thread started");
}
In the above code, the return value from 'async' will be destroyed at the
}, meaning the threads would run consecutively rather than concurrently. If
you want it to persist up one level, then you double it:
for ( unsigned i = 0u; i < N, ++i )
{
%%async(Func);
Log("new thread started");
}
And if you want it to persist up another level above that, you triple it:
for ( unsigned i = 0u; i < ROWS; ++i )
{
for ( unsigned j = 0u; j < COLS; ++j )
{
%%%async(Func);
Log("new thread started");
}
}
This could allow some really cool stuff to happen, for instance let's say
we were to give 'std::mutex' a method as follows:
lock_guard<mutex> mutex::autolock(void)
We could then lock a mutex as follows:
mutex m;
int main(void)
{
m.%autolock(); // or: %m.autolock() ?
DoSomething();
}
We could expand the scope of the lock:
int main(void)
{
if ( a )
{
if ( b || c )
{
m.%%%autolock(); // or: %%%m.autolock() ?
}
}
DoSomething();
}
In any function body that contains use of %% or %%%, the compiler would
have to allocate space on the stack upon entering the function in order to
manage the might-or-might-not-be-created objects. This would involve the
use of 'alloca' along with memory addresses of destructors that need to run.
>
>
> Yeah, I don't want spooky changes to the lifetime model and how it
> interacts with scopes to happen implicitly based on something far away on
> the function declaration. If somebody adds __persistent to a declaration,
> it silently changes the meaning of existing callers. No thanks.
>
What if we were to move the 'mark' to the calling site. Perhaps a unary
operator? So the following:
int main(void)
{
%async(Func);
}
would be the same as:
int main(void)
{
auto dummy = async(Func);
}
Of course this begs the question, what about loops?
for ( unsigned i = 0u; i < N; ++i ) %async(Func);
Well I think in the above line, the scope of the return value should be to
the nearest preceding {, meaning that after the loop has finished, you have
N objects on the stack as the N threads run concurrently (implemented by
the compiler by calling 'alloca' inside the loop with linked list).
But what if the loop has a curly-braced body?
for ( unsigned i = 0u; i < N, ++i )
{
%async(Func);
Log("new thread started");
}
In the above code, the return value from 'async' will be destroyed at the
}, meaning the threads would run consecutively rather than concurrently. If
you want it to persist up one level, then you double it:
for ( unsigned i = 0u; i < N, ++i )
{
%%async(Func);
Log("new thread started");
}
And if you want it to persist up another level above that, you triple it:
for ( unsigned i = 0u; i < ROWS; ++i )
{
for ( unsigned j = 0u; j < COLS; ++j )
{
%%%async(Func);
Log("new thread started");
}
}
This could allow some really cool stuff to happen, for instance let's say
we were to give 'std::mutex' a method as follows:
lock_guard<mutex> mutex::autolock(void)
We could then lock a mutex as follows:
mutex m;
int main(void)
{
m.%autolock(); // or: %m.autolock() ?
DoSomething();
}
We could expand the scope of the lock:
int main(void)
{
if ( a )
{
if ( b || c )
{
m.%%%autolock(); // or: %%%m.autolock() ?
}
}
DoSomething();
}
In any function body that contains use of %% or %%%, the compiler would
have to allocate space on the stack upon entering the function in order to
manage the might-or-might-not-be-created objects. This would involve the
use of 'alloca' along with memory addresses of destructors that need to run.
Received on 2026-05-23 07:36:54
