Pass-by-lambda

794 views
Skip to first unread message

Hyman Rosen

unread,
May 14, 2014, 4:52:03 PM5/14/14
to std-pr...@isocpp.org
I propose to allow function parameter declarations to be prefixed with [&].  Doing so would cause those parameters to be passed by lambda. That is, when the function is called, the argument expression for that parameter is not evaluated.  Instead, it is wrapped in a 0-parameter capture-by-reference lambda that returns that expression, and the lambda is passed to the function.  When the function would access the variable. it calls the lambda instead and uses its return value (and it may do so any number of times, including 0).

Among other things, this could be used to finally implement user-defined operator&& and operator|| with proper short-circuiting:

class A
{
    bool is() const;

  public:
    bool operator&&([&] A other) const { return is() && other.is(); }
    bool operator||([&] A other) const { return is() || other.is(); }
};

A f();
A g();

void test()
{
    if (f() && g()) { ... }  // Calls g() only if f().is()
    if (f() || g()) { ... }  // Calls g() only if !f().is()
}

The above code is equivalent to

class A
{
    bool is() const;

  public:
    bool operator&&(A (*other)()) const { return is() && other().is(); }
    bool operator||(A (*other)()) const { return is() || other().is(); }
};

A f();
A g();

void test()
{
    if (f().operator&&([&] { return g(); }) { ... }
    if (f().operator||([&] { return g(); }) { ... }
}

Another example:

template <typename T>
void repeat(size_t n, [&] T t) { while (n-- > 0) { t; } }

void test()
{
    int i = 1;

    repeat(5, i += 2);
    assert(i == 11);

    repeat(0, i += 2);
    assert(i == 11);
}

David Rodríguez Ibeas

unread,
May 14, 2014, 6:23:54 PM5/14/14
to std-pr...@isocpp.org
From a user point of view, I am not too fond of the idea of the lambda being evaluated on each use inside the function. This is screaming about well known issues with macros. If the expression that is mapped to the lambda has any side effects, it would not be visible to the caller how many times those side effects will be evaluated.

void f([&] int i);
int i = 0;
f(++i);          // What is the value of 'i' here? Cannot be known without seeing the implementation of 'f'

Additionally if the argument is an rvalue-expression, you would have a different issue, in code that looks fine from the point of view of the function:

int accumulate([&] std::vector<int> v) {
    return std::accumulate(v.begin(), v.end(), 0);
}

Without knowing how this will be called, 'v' might be an rvalue-expression, and 'v.begin()' and 'v.end()' might be iterators into two different containers. To some extent this could be worked around by carefully implementing the function:

int accumulate([&] std::vector<int> v) {
    const auto& local = v;
    return std::accumulate(local.begin(), local.end(), 0);
}

To this respect,  it seems more appropriate to require that the compiler evaluates the lambda at most once if the argument is actually evaluated. The implementation of 'operator||' in the original code would be transformed by the compiler into something like:

bool operator||( [&] A a) const {
    if (is()) return true;
    else {
        auto &&__tmp = a(); // evaluation of the lambda only once, and only if needed
        return __tmp.is();
    }
}

This would break the use case for 'repeat', but I believe this is saner than supporting 'repeat' at the cost of not knowing how many times it is going to be evaluated (in other functions) or the possibilities for mistakes in the implementation of the function assuming that the argument is an object and not the expression being evaluated every time.

I am not sure how to properly encode const-volatile into the syntax. The argument is really a callable entity, but it is designed to look like an object. Should the presence of 'const', 'volatile' or l-/r-value references affect the callable? (probably not) The result of the expression? I am not sure how to map that. But that is not the harder problem. 

A lower level problem is the fact that stateful lambdas cannot be converted to regular function pointers, which means that the equivalence relationship in the original text is broken, and raises the question of what would the argument to the function really be.  One option is to restrict this to templates, in which case it will be mapped directly to a lambda generated on the fly at the place of call for each lambda-argument. But this limits the use for the 'operator||' and 'operator&&'. 

If this needs to be available outside of templates, a vocabulary type that does type-erasure would be needed. The obvious choice here would be 'std::function<T&&()>' for a type 'T' deduced from the expression, but this means that more often than not there will be a dynamic allocation per call to the function (this overhead is not unique to 'std::function' really, it is rather a side effect of requiring type-erasure).

   David



--

---
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.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Diggory Blake

unread,
May 14, 2014, 8:29:22 PM5/14/14
to std-pr...@isocpp.org
Making them implicit template parameters would give best performance and maps most closely to the way the compiler generates code for the existing short-circuit operators.

Jeremy Maitin-Shepard

unread,
May 15, 2014, 2:03:32 AM5/15/14
to std-pr...@isocpp.org, dib...@ieee.org


On Wednesday, May 14, 2014 3:23:54 PM UTC-7, David Rodríguez Ibeas wrote:
From a user point of view, I am not too fond of the idea of the lambda being evaluated on each use inside the function. This is screaming about well known issues with macros. If the expression that is mapped to the lambda has any side effects, it would not be visible to the caller how many times those side effects will be evaluated.


It seems to me that the function should be allowed to evaluate the argument multiple times.  However, to avoid confusion, there should be no implicit evaluation: the lambda parameters should have the type of a functor.  To evaluate, the function uses the usual function call syntax.  Thus, it would only be syntactic sugar for the call site.

 



If this needs to be available outside of templates, a vocabulary type that does type-erasure would be needed. The obvious choice here would be 'std::function<T&&()>' for a type 'T' deduced from the expression, but this means that more often than not there will be a dynamic allocation per call to the function (this overhead is not unique to 'std::function' really, it is rather a side effect of requiring type-erasure).

Actually, type erasure does not require dynamic allocation if the function type does not have to be copyable.  There is still the virtual function call overhead, though.  It seems there needs to be a way to specify the true function type of the argument.  Perhaps the type specified in the function signature should be the function type rather than the return value of the function.  That would seem to solve the problem nicely.

Nevin Liber

unread,
May 15, 2014, 1:55:51 PM5/15/14
to std-pr...@isocpp.org
On 14 May 2014 15:52, Hyman Rosen <hyman...@gmail.com> wrote:
I propose to allow function parameter declarations to be prefixed with [&].  Doing so would cause those parameters to be passed by lambda. That is, when the function is called, the argument expression for that parameter is not evaluated.  Instead, it is wrapped in a 0-parameter capture-by-reference lambda that returns that expression, and the lambda is passed to the function.  When the function would access the variable. it calls the lambda instead and uses its return value (and it may do so any number of times, including 0).

template <typename T>
void repeat(size_t n, [&] T t) { while (n-- > 0) { t; } }

void test()
{
    int i = 1;

    repeat(5, i += 2);
    assert(i == 11);

    repeat(0, i += 2);
    assert(i == 11);
}

As I understand it, this is just syntactic sugar for:

template <typename F>
void repeat(size_t n, F const& f) { while (n-- > 0) { f(); } }


void test()
{
    int i = 1;

    repeat(5, [&]{i += 2;});
    assert(i == 11);

    repeat(0, [&]{i += 2;});
    assert(i == 11);
}

The latter is certainly more flexible (I don't have to use lambdas; I don't have to capture by reference, etc.) than your proposal.  

And as has already been pointed out, you really have to understand the callee to know what will happen to the caller's variables, especially with respect to exceptions being thrown.  That being said, it probably isn't that much worse than passing by non-const reference (which is why many of us prefer to return by value most of the time instead of passing in something to be modified).

Other than the short circuit case, what are the other compelling use cases?
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com(847) 691-1404

Nevin Liber

unread,
May 15, 2014, 1:59:21 PM5/15/14
to std-pr...@isocpp.org
On 15 May 2014 01:03, Jeremy Maitin-Shepard <jer...@jeremyms.com> wrote:
Actually, type erasure does not require dynamic allocation if the function type does not have to be copyable. 

Not true.  The only way it does not require dynamic allocation is if you know all the types being erased (in which case you can just use something like Boost.Variant).  How else can you calculate at compile time the largest size the type erased object needs to be to hold all possible erased types?

Ville Voutilainen

unread,
May 15, 2014, 2:00:40 PM5/15/14
to std-pr...@isocpp.org
On 15 May 2014 20:55, Nevin Liber <ne...@eviloverlord.com> wrote:
> As I understand it, this is just syntactic sugar for:
> template <typename F>
> void repeat(size_t n, F const& f) { while (n-- > 0) { f(); } }
> The latter is certainly more flexible (I don't have to use lambdas; I don't
> have to capture by reference, etc.) than your proposal.

Nitpick-time! :) You can't use that with a mutable lambda that captures this. ;)

> And as has already been pointed out, you really have to understand the
> callee to know what will happen to the caller's variables, especially with
> respect to exceptions being thrown. That being said, it probably isn't that
> much worse than passing by non-const reference (which is why many of us
> prefer to return by value most of the time instead of passing in something
> to be modified).
>
> Other than the short circuit case, what are the other compelling use cases?


I have my doubts whether allowing short-circuiting in user-defined logical
operators _is_ a compelling use case.

Ville Voutilainen

unread,
May 15, 2014, 2:08:27 PM5/15/14
to std-pr...@isocpp.org
On 15 May 2014 21:00, Ville Voutilainen <ville.vo...@gmail.com> wrote:
> Nitpick-time! :) You can't use that with a mutable lambda that captures this. ;)

Or actually with any capturing mutable lambda...

Jeremy Maitin-Shepard

unread,
May 15, 2014, 2:24:45 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 10:59 AM, Nevin Liber <ne...@eviloverlord.com> wrote:
On 15 May 2014 01:03, Jeremy Maitin-Shepard <jer...@jeremyms.com> wrote:
Actually, type erasure does not require dynamic allocation if the function type does not have to be copyable. 

Not true.  The only way it does not require dynamic allocation is if you know all the types being erased (in which case you can just use something like Boost.Variant).  How else can you calculate at compile time the largest size the type erased object needs to be to hold all possible erased types?

The functor can live in the stack frame of the caller (or elsewhere).  The caller just supplies a pointer/reference to the functor.  If we assume that the functor is not copyable, then the callee does not need to know how to copy the functor or even its size.  The functor (possibly via a wrapper) inherits from a base with a virtual operator(), or the caller also supplies a function pointer for invoking the functor.

This is all easily implementable via a function_ref type which would be safe for use as a function parameter type but which could easily result in dangling references if used for other purposes.  Possibly it would be a good addition to the standard library as a way to avoid the copying and memory allocation of std::function in cases where value semantics are not required.

David Rodríguez Ibeas

unread,
May 15, 2014, 2:39:46 PM5/15/14
to std-pr...@isocpp.org
This is orthogonal to the thread, but you got me interested:

On Thu, May 15, 2014 at 2:24 PM, Jeremy Maitin-Shepard <jer...@jeremyms.com> wrote:

This is all easily implementable via a function_ref type which would be safe for use as a function parameter type but which could easily result in dangling references if used for other purposes.  Possibly it would be a good addition to the standard library as a way to avoid the copying and memory allocation of std::function in cases where value semantics are not required.

Can you not do this with 'std::ref' now?

Diggory Blake

unread,
May 15, 2014, 2:41:12 PM5/15/14
to std-pr...@isocpp.org

You can't put it on the stack because the lambda may be used after the function returns, putting it anywhere else requires an allocation (it can't be static). There is the special case where the lambda will never be used outside the function, in that case you could add a constructor to std::function which would allow this:

auto f = <some lambda>;
std
::function g(std::ref(f));

In this case it's clear that returning 'g' would be an error, but it can still be used within the scope without a single allocation.

Having said that, you might just be able to do this:
auto f = <some lambda>;
std
::function g(&f);

Although I'm not sure if the call operator would work properly though an indirection, I suspect that the fact that it does so on function pointers is just a special case.




Nevin Liber

unread,
May 15, 2014, 2:41:01 PM5/15/14
to std-pr...@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.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.

Jeremy Maitin-Shepard

unread,
May 15, 2014, 2:59:43 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 11:41 AM, Diggory Blake <dig...@googlemail.com> wrote:

You can't put it on the stack because the lambda may be used after the function returns, putting it anywhere else requires an allocation (it can't be static). There is the special case where the lambda will never be used outside the function, in that case you could add a constructor to std::function which would allow this:

auto f = <some lambda>;
std
::function g(std::ref(f));

In this case it's clear that returning 'g' would be an error, but it can still be used within the scope without a single allocation.

You have a good point.  Assuming a small buffer optimization, using std::ref with std::function achieves the same thing as the hypothetical function_ref.  However, a function_ref that can be constructed from an rvalue would still be useful, as it would allow you to do:

void foo(function_ref<void ()> x);

foo([&] { /* ... */ });

instead of the less convenient

void foo(std::function<void ()> x);
auto f = [&] { /* ... */ };
foo(std::ref(f));

Nicola Gigante

unread,
May 15, 2014, 3:17:24 PM5/15/14
to std-pr...@isocpp.org, Nicola Gigante
Hi!

I have proposed something like this here before,
look at
http://thread.gmane.org/gmane.comp.lang.c++.isocpp.proposals/9988

In my opinion, it would be a great extension to the language, to enable
programmers to write even more functional-style code.
Regarding use cases, overloading && and || is not the only one.
Whoever has ever written code in some functional language knows
how lazy function calls can be useful.

As far as your proposal is concerned, with respect to mine, you
suggest to have explicit call syntax inside the function, while it
would be transparent at call site.

I have proposed something more transparent. In a few words,
the evaluation of the parameter lambda would happen at the
first use of the object, and the result stored in an actual local
variable. This ensure the side effects of the argument to happen
only once, if at all.

I exclude the possibility to evaluate the argument's side effects more
than once, because it could be difficult to take into account all the
corner cases. However, this can be debated.

Anyway, my idea have not been very much welcomed, but later
I've seen something like this have been proposed by other people
other times.

So if there is interest we can make up a paper formalizing our ideas.
Is there somebody interested?


Greetings,
Nicola

Diggory Blake

unread,
May 15, 2014, 3:20:43 PM5/15/14
to std-pr...@isocpp.org
That requires "foo" to be declared differently, which I think is undesirable, std::function is supposed to be the most generic possible form which you can use in any situation.

I'm not sufficiently familiar with the exact rules for when the lifetime of a temporary is extended, but it seems unclear at exactly what point a function_ref will become invalid. You're essentially creating a wrapper for rvalue references, which I think is inherently unsafe, and the reason why std::ref and reference_wrapper are incompatible with them. By storing the lambda in a local variable it's completely unambiguous when its lifetime ends, and when it will become invalid.

Jeremy Maitin-Shepard

unread,
May 15, 2014, 3:38:33 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 12:20 PM, Diggory Blake <dig...@googlemail.com> wrote:
That requires "foo" to be declared differently, which I think is undesirable, std::function is supposed to be the most generic possible form which you can use in any situation.

An alternative would be an rvalue_ref function that returns a reference_wrapper for rvalues.  That could be used with std::function, though it would be less convenient syntax at the call site.  However, the advantage of function_ref is that it documents that reference semantics are expected.

A related issue is that std::function does not support move-only functions; this can be worked around by a wrapper as well.
 
I'm not sufficiently familiar with the exact rules for when the lifetime of a temporary is extended, but it seems unclear at exactly what point a function_ref will become invalid. You're essentially creating a wrapper for rvalue references, which I think is inherently unsafe, and the reason why std::ref and reference_wrapper are incompatible with them. By storing the lambda in a local variable it's completely unambiguous when its lifetime ends, and when it will become invalid.

The lifetime is valid until the end of the statement.  It isn't inherently unsafe, it just requires care.  Passing by const reference has the same potential for dangling references.  It would be somewhat dangerous to use function_ref as a local variable, since it doesn't even do the lifetime extension of temporaries that a true reference type would, though that is still very error prone.

Diggory Blake

unread,
May 15, 2014, 3:47:08 PM5/15/14
to std-pr...@isocpp.org

Yeah, I was mainly thinking of if you wanted to use function_ref as a local variable.

Also, std::ref should just work in your example too, as long as the lambda isn't mutable:
void foo(function<void ()> x);

foo
(std::ref([&] { /* ... */ }));

It would just be a const reference.

If you want to use a mutable lambda, then perhaps it's better that you have to provide your own storage for it?


Jeremy Maitin-Shepard

unread,
May 15, 2014, 5:13:29 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 12:47 PM, Diggory Blake <dig...@googlemail.com> wrote:

Also, std::ref should just work in your example too, as long as the lambda isn't mutable:
void foo(function<void ()> x);

foo
(std::ref([&] { /* ... */ }));

This doesn't work because std::ref explicitly deletes the rvalue overloads.

Diggory Blake

unread,
May 15, 2014, 7:28:12 PM5/15/14
to std-pr...@isocpp.org
But I can do this:
template<typename T> T const& test(T const& v) {
   
return v;
}

int main()
{
   
auto i = std::ref(test([](int a){ return a + 1; }));
   
   
return 0;
}

It seems like there should be a way to tell ref/cref to use the const& overload instead of trying to use the rvalue overload and failing.

Brian Bi

unread,
May 15, 2014, 7:36:04 PM5/15/14
to std-pr...@isocpp.org
But the lifetime of the temporary won't be extended, so even if you could get a std::reference_wrapper to a temporary, it would be useless because the temporary will have already been destroyed before the beginning of the next full-expression.

--
Brian Bi



--

Jeremy Maitin-Shepard

unread,
May 15, 2014, 7:52:24 PM5/15/14
to std-pr...@isocpp.org
On Thu, May 15, 2014 at 4:36 PM, Brian Bi <bbi...@gmail.com> wrote:
But the lifetime of the temporary won't be extended, so even if you could get a std::reference_wrapper to a temporary, it would be useless because the temporary will have already been destroyed before the beginning of the next full-expression.

It would be useless to store the result in a local variable, but it is fine to pass the result as a function argument directly.

Ivan Čukić

unread,
May 16, 2014, 7:03:21 PM5/16/14
to std-pr...@isocpp.org
Hi,

This does not really require any changes to the core language.

Doing lazy evaluation It is quite trivial and common (albeit not always intentional*). You can write a templated class lazy<T> that would evaluate any functor when the value is first requested, and cache the result for future reference (a proper lazy evaluation).

Differences between
    [&] std::string s
    lazy std::string s
    lazy<std::string> s
are not really significant to warrant new syntax.

The class itself might be a nice addition to STL.

Cheerio,
Ivan

* Expression templates are based on lazy evaluation. Some people do find it unexpected, especially since C++11 and 'auto'.

Diggory Blake

unread,
May 16, 2014, 11:11:36 PM5/16/14
to std-pr...@isocpp.org
I think you're missing the point - the idea in the OP is that the caller does not have to specify the lambda explicitly, instead they'd just pass in a value as they would normally and the compiler would implicitly turn the expression into a lambda.

Ivan Čukić

unread,
May 17, 2014, 5:56:34 PM5/17/14
to std-pr...@isocpp.org
> I think you're missing the point - the idea in the OP is that the caller does not have to

True, got sidetracked by Nicola's proposal.

> specify the lambda explicitly, instead they'd just pass in a value as they would

So, this would be only passing non-evaluated expressions to functions, without wrapping them in lambda syntax.

While this could be (really) fun, I think it would open a can of worms.

If you can wrap an expression without free variables, people would want it generalized to those with free vars, and we would get a truly great syntax (like boost.phoenix) of std::accumulate(b, e, _1 * _2).

Unfortunately, these terse-syntax lambda proposals were rejected.

Cheers,
Ivan

jgot...@gmail.com

unread,
May 18, 2014, 10:35:59 PM5/18/14
to std-pr...@isocpp.org
This is implemented in the D programming language as  lazy parameters .  The use case they use in their example is logging. Programmers often want to log a message conditionally, depending on a global log level variable (so they print all messages in debug mode, but only important ones in release mode.  The naive way to write this function is:

void logMessage(int level, const std::string &message) {
   
if (level < globalLogLevel) {
        std
::cout << message;
   
}
}

But, since the messages sent to a logger are often complicated string that are the result of appending one or more strings to each otther, this naive implementation can often end up constructing the temporary strings even when the log level is such nothing will be printed.  To avoid this, users often use logging macros to avoid creating the temporary. If we could declare message as a lazy parameter we would not need to use a macro.

Jim Porter

unread,
May 19, 2014, 12:22:55 AM5/19/14
to std-pr...@isocpp.org
On Wednesday, May 14, 2014 3:52:03 PM UTC-5, Hyman Rosen wrote:
I propose to allow function parameter declarations to be prefixed with [&].  Doing so would cause those parameters to be passed by lambda. That is, when the function is called, the argument expression for that parameter is not evaluated.  Instead, it is wrapped in a 0-parameter capture-by-reference lambda that returns that expression, and the lambda is passed to the function.  When the function would access the variable. it calls the lambda instead and uses its return value (and it may do so any number of times, including 0).

Overall, this seems at least moderately useful (it would allow short-circuiting boolean operators and macro-free debug/logging functions), but I'd much rather see the parameter be treated as a function object. This is more explicit, and is consistent with other areas of C++ as well (e.g. container.size() is a function, not a getter like in other languages*). That would make it extremely obvious to the user of pass-by-lambda where the passed expression is evaluated. I think that obviousness outweighs the minor ugliness of the syntax:

struct A {
  bool is() const;

  bool operator &&([&] A rhs) {
    return is() && rhs().is();
  }
};

- Jim

* That's not to say that getters would be entirely bad in C++, but I'd rather not see *arbitrary* expressions look like getters; only ones that are explicitly written to be a getter.

David Krauss

unread,
May 19, 2014, 12:32:07 AM5/19/14
to std-pr...@isocpp.org
On 2014–05–15, at 4:52 AM, Hyman Rosen <hyman...@gmail.com> wrote:

I propose to allow function parameter declarations to be prefixed with [&].  Doing so would cause those parameters to be passed by lambda. That is, when the function is called, the argument expression for that parameter is not evaluated.  Instead, it is wrapped in a 0-parameter capture-by-reference lambda that returns that expression, and the lambda is passed to the function.  When the function would access the variable. it calls the lambda instead and uses its return value (and it may do so any number of times, including 0).

This looks very similar to my recent proposal (only a thread here, see OP on April 9) for inline variables. The inline keyword is proposed to set a variable to refer to its initializer, which is not immediately evaluated. The behavior is somewhat like a lambda functor. The difference from your proposal is avoidance of the () function call operator.

I prohibited it for function parameters, but inline parameters may be permissible on inline functions — which is a simple rule to remember.

If taken merely as a suggestion, as inline is for functions, this does not require templating. Such a system would allow evaluation either eagerly or lazily, and telling the difference would be UB, which is probably a very bad thing. It would not (reliably) support short-circuit operator overloads as you describe.

If inline expresses a requirement on parameters, then they behave like templates, and function recursion equates to template recursion. This seems like an equally bad thing. A workaround may be to special-case function calls where an inline variable is passed to an inline parameter, such that the invisible lambda is passed directly rather than being evaluated and then re-wrapped. The problem may be (maybe) minimized by avoiding specification of implicit templating.

The compromise viz. hidden templating is that an inline function with an inline parameter may not have its address taken. Does anyone see another gotcha?

David Krauss

unread,
May 19, 2014, 12:40:40 AM5/19/14
to std-pr...@isocpp.org

On 2014–05–19, at 12:32 PM, David Krauss <pot...@gmail.com> wrote:

> The compromise viz. hidden templating is that an inline function with an inline parameter may not have its address taken. Does anyone see another gotcha?

Oh, that’s not a limitation, it’s more subtle. A function with inline parameters may have a number of object code implementations, each with linkage, for its various non-inlined call sites. These would all be effectively anonymous. Taking the address would effectively refer to a different object code with the inline specifiers discarded. This makes sense because inline does not form part of the type of a variable, and hence it’s not part of the function signature either.

The inline specifier should be prohibited from parameters in function abstract-declarators, that’s all.

Matthew Woehlke

unread,
May 21, 2014, 1:22:05 PM5/21/14
to std-pr...@isocpp.org
On 2014-05-14 18:23, David Rodríguez Ibeas wrote:
> On Wed, May 14, 2014 at 4:52 PM, Hyman Rosen wrote:
>> I propose to allow function parameter declarations to be prefixed with [&]*.
>> *Doing so would cause those parameters to be *passed by lambda*. That
>> is, when the function is called, the argument expression for that parameter
>> is not evaluated. Instead, it is wrapped in a 0-parameter
>> capture-by-reference lambda that returns that expression, and the lambda is
>> passed to the function. When the function would access the variable. it
>> calls the lambda instead and uses its return value (and it may do so any
>> number of times, including 0).

This has come up before, but this seems like a reasonable syntax for the
feature. I like!

However...

> From a user point of view, I am not too fond of the idea of the lambda
> being evaluated on each use inside the function.

I must strongly agree with this; if the lambda is evaluated at all, it
must be evaluated exactly once. In any other direction lies madness.

That said, the whole *point* of the feature is to allow that the side
effects are *not* evaluated. Provided the lambda is evaluated zero or
one time(s), I don't see a technical problem. (Functions using this will
of course need to be well documented, but that's not the standard's
problem.)

> This would break the use case for 'repeat'

IMO something like 'repeat' should take an explicit lambda.

> I am not sure how to properly encode const-volatile into the syntax.

I don't think there is an issue here; the type w/o '[&]' is the return
type of the lambda, which may have CV qualifiers as usual. All we're
doing is moving the execution point of the code from caller to callee.

As an aside... this is somewhat covered elsewhere, but I'd probably try
to implement this at the compiler level without having "traditional"
types defined for the "functor". IOW, the actual parameter passed would
be a pair of pointers providing an instruction address and an opaque
pointer (e.g. stack frame of the caller, pointer to a single variable
being manipulated, etc.); to evaluate the parameter, the callee provides
the stack address in a well known location (e.g. register) and jumps to
the instruction address. Further, the compiler would be free to elide
all of this if the callee is inlined. The use site is responsible for
ensuring the lambda is only evaluated once.

This assumes of course that the lambda is not copyable, but I'd say
that's a given. Use of a "lambda" is more of an implementation detail;
the feature is really that the code that provides the parameter value is
not executed immediately (or, possibly, at all).

IOW:

void foo([&] int bar)
{
// This *evaluates* the lambda; no copying is allowed
something = bar;
}

Similar with returning the parameter. (I'll leave open to discussion if
an inline function returning a delayed parameter value may delay
evaluation even further after the call site. But probably not.)

Relative to the above, I *do* think there is benefit to having this as a
language feature, especially if the dispatch uses a fast calling
convention (i.e. the "lambda" is responsible for all stack
manipulations, register saving, etc., which would allow these to be
elided entirely in some cases). This isn't possible with e.g. std::lazy,
both because the caller must explicitly pass a lambda (makes Boolean
operators especially ugly), and because there is a minimum overhead that
the compiler cannot elide.

--
Matthew

Nicola Gigante

unread,
May 22, 2014, 1:59:45 AM5/22/14
to std-pr...@isocpp.org
Il giorno 21/mag/2014, alle ore 19:22, Matthew Woehlke <mw_t...@users.sourceforge.net> ha scritto:
>
>> On 2014-05-14 18:23, David Rodríguez Ibeas wrote:
>>> On Wed, May 14, 2014 at 4:52 PM, Hyman Rosen wrote:
>>> I propose to allow function parameter declarations to be prefixed with [&]*.
>>> *Doing so would cause those parameters to be *passed by lambda*. That
>>> is, when the function is called, the argument expression for that parameter
>>> is not evaluated. Instead, it is wrapped in a 0-parameter
>>> capture-by-reference lambda that returns that expression, and the lambda is
>>> passed to the function. When the function would access the variable. it
>>> calls the lambda instead and uses its return value (and it may do so any
>>> number of times, including 0).
> This has come up before, but this seems like a reasonable syntax for the
> feature. I like!

> However...
>
>> From a user point of view, I am not too fond of the idea of the lambda
>> being evaluated on each use inside the function.
>
> I must strongly agree with this; if the lambda is evaluated at all, it
> must be evaluated exactly once. In any other direction lies madness.
>

I agree! This is more in line with the functional spirit of such a proposal. That's easy achievable by making sure that the wrapping lambda caches the result in a local variable the first time it is called.

> That said, the whole *point* of the feature is to allow that the side
> effects are *not* evaluated. Provided the lambda is evaluated zero or
> one time(s), I don't see a technical problem. (Functions using this will
> of course need to be well documented, but that's not the standard's
> problem.)
>
>> This would break the use case for 'repeat'
>
> IMO something like 'repeat' should take an explicit lambda.
>
>> I am not sure how to properly encode const-volatile into the syntax.
>
> I don't think there is an issue here; the type w/o '[&]' is the return
> type of the lambda, which may have CV qualifiers as usual. All we're
> doing is moving the execution point of the code from caller to callee.
>

It can be not so simple, consider:

void log(std::string const& message) {
if(have_logs)
std::cerr << message;
}

This is fine while this is not:

void log(std::string const& [&] message) {
if(have_logs)
std::cerr << message;
}

This would mean that the implicit lambda is _returning_ a reference to a const string? inside the lambda it would be in 90% of cases a local temporary, and you're returning a dangling reference, unless temporaries of the argument expression don't get different rules for their lifetime.

> As an aside... this is somewhat covered elsewhere, but I'd probably try
> to implement this at the compiler level without having "traditional"
> types defined for the "functor". IOW, the actual parameter passed would
> be a pair of pointers providing an instruction address and an opaque
> pointer (e.g. stack frame of the caller, pointer to a single variable
> being manipulated, etc.); to evaluate the parameter, the callee provides
> the stack address in a well known location (e.g. register) and jumps to
> the instruction address. Further, the compiler would be free to elide
> all of this if the callee is inlined. The use site is responsible for
> ensuring the lambda is only evaluated once.
>
> This assumes of course that the lambda is not copyable, but I'd say
> that's a given. Use of a "lambda" is more of an implementation detail;
> the feature is really that the code that provides the parameter value is
> not executed immediately (or, possibly, at all).
>
> IOW:
>
> void foo([&] int bar)
> {
> // This *evaluates* the lambda; no copying is allowed
> something = bar;
> }
>
> Similar with returning the parameter. (I'll leave open to discussion if
> an inline function returning a delayed parameter value may delay
> evaluation even further after the call site. But probably not.)
>

If you allow "delayed" return values you should make sure local variables are captured by value (while the ones in the case of a function argument can and should be captured by reference). Maybe by allow the user to write [=] instead of [&].

Now, if a function has a delayed return value and returns a delayed parameter, the parameter should not be evaluated, exactly as any other part of the returning expression:

int [=] foo(int n) {
return n * 2; // here n it's not evaluated
}

int [=] foo(int [&] n) {
return n * 2; // here it shouldn't either
}

Note that here (IMHO) the delayed parameter would capture caller's variables by reference, while the return value captures by value, in this case meaning that it captures by value the variable 'n', but not the variables captured by 'n' (this is in line with what would happen with an explicit lambda)

> Relative to the above, I *do* think there is benefit to having this as a
> language feature, especially if the dispatch uses a fast calling
> convention (i.e. the "lambda" is responsible for all stack
> manipulations, register saving, etc., which would allow these to be
> elided entirely in some cases). This isn't possible with e.g. std::lazy,
> both because the caller must explicitly pass a lambda (makes Boolean
> operators especially ugly), and because there is a minimum overhead that
> the compiler cannot elide.
>

It has to be considered the fact that a function taking explicitly a lambda would be a template function, while if you allow such a delayed parameter in non-template functions then you have to implement the feature with a sort of type erasure for the lambda, which disable a lot of optimizations. Of course this could be simply be an implementation problem, and restricting delayed parameters to template functions for this reason seems artificial. Nevertheless the standard wording should be clear that an implementation is allowed to make multiple instantiations of a function template with a delayed parameter, to allow it to avoid type erasure when it's possible.

> --
> Matthew
>

Bye,
Nicola

Diggory Blake

unread,
May 22, 2014, 2:29:07 AM5/22/14
to std-pr...@isocpp.org


On Thursday, 22 May 2014 06:59:45 UTC+1, Nicola Gigante wrote:
It can be not so simple, consider:

void log(std::string const& message) {
   if(have_logs)
      std::cerr << message;
}

This is fine while this is not:

void log(std::string const& [&] message) {
   if(have_logs)
      std::cerr << message;
}

This would mean that the implicit lambda is _returning_ a reference to a const string? inside the lambda it would be in 90% of cases a local temporary, and you're returning a dangling reference, unless temporaries of the argument expression don't get different rules for their lifetime.

The compiler is free to generate whatever code it wants, it doesn't have to be limited to the types of lambdas that are easy to write in C++. For example:

lazy_func(a + b);

Instead of the compiler just wrapping the expression 'a + b' in a lambda, you could specify that the compiler should generate a lambda which when invoked will be equivalent to jumping out of the function "lazy_func", evaluating "a + b" as though it was just a normal parameter, and then jumping back into "lazy_func" at the point where it left off. It would be exactly the inverse of how "yield return" works in C#.

In short, the lifetimes of expressions in the parameter list would end at the same time as if they were not being lazily evaluated. Passing by reference would still work correctly with no difference from before.

Nicola Gigante

unread,
May 22, 2014, 3:27:52 AM5/22/14
to std-pr...@isocpp.org
Of course the compiler can do it, but I’m not sure it’s exactly the right approach. Usually, temporaries’ lifetime is that of their full expression, 
and they get destroyed in the reverse order of creation, right? It seems to me that this means that the caller has to run different cleanups for 
the case where the callee has evaluated the parameter and the case where it has not, which to me sounds weird and complex to implement.
It also adds complexity to the calling convention.

Rather than to state the temporaries’ lifetime in terms of what could have happened if the argument were not passed by lambda, I’d make 
them belong to the callee scope, which sounds more clear to me. Their lifetime could be that of local variables of the callee (like any other argument). This seems to me more natural, and that’s what I was suggesting in my last email, I’m sorry if I’ve not explained myself very 
clearly.

Bye,
Nicola




David Rodríguez Ibeas

unread,
May 22, 2014, 11:18:20 AM5/22/14
to std-pr...@isocpp.org
I am trying to grasp all of the implications, but in the meantime just a comment:


On Thu, May 22, 2014 at 1:59 AM, Nicola Gigante <nicola....@gmail.com> wrote:
This would mean that the implicit lambda is _returning_ a reference to a const string? inside the lambda it would be in 90% of cases a local temporary, and you're returning a dangling reference, unless temporaries of the argument expression don't get different rules for their lifetime.


The suggestion for guaranteeing the single evaluation of the function was to have the lambda 'cache' the value on the first execution. The (not-really-a) lambda would have a member (std::optional-like) holding the value and return a reference to that.

[I meant the above to be the "just a comment", but I seem to have overdone the "just" below]:

I am not sure this is the correct approach, I was thinking on a different possible implementation: it is up to the function to evaluate the lambda *once* and cache the result.

In both cases the cv-qualifiers and reference types are tricky to handle. I guess part of the problem I am having here is that the syntax looks the same as a lambda definition syntax, but the semantics must be different. The function signature does not deal with how the arguments are created, but how they are going to be used. That is, the [&] is unrelated to how the compiler generated lazy evaluation will capture the environment. The type and cv-qualifiers relate to how the function will use the result of the lambda, not what the expression yields.

Given:

void f(std::string const & [&] arg);
std::string value();
std::string & reference();

For the use case 'f(value())', the generated code would have to call the function, store the returned value inside the lambda and then return a reference to that cached value. But in the case of 'f(reference())' the generated code would have to store a pointer to the result of 'reference()' and then return the result of dereferencing that pointer. Up to here it seems sane enough.

Now if the function was:

void g(std::string [&] arg);

I imagine that in this case the generated code would have to be the same, but return from the internal storage by value, which means that in the case of 'g(value())' two copies of the string would have to be taken. (The generated code not knowing how many times it is going to be called would evaluate 'value()' and store it internally, the first evaluation would then have to create a second copy from the cache into the context of 'g'). In the case of 'g(reference())' the generated code could still store the pointer, and do the copies on demand.

While this provides some sane semantics from the point of view of the implementation of the generator for the lambda, it would be at the very least surprising if the implementation of 'g' looked like this:

void g(std::string [&] arg) {
    arg = "the new value";
    std::cout << arg;                  // This is not "the new value"!!!
}

In the same way, it would be up for discussion whether this third option should be allowed:

void h(std::string & [&] arg);
h(value());                              // compiler error?

Should that be allowed? The instinctive answer is that it should not, but this case is not all that different from 'std::bind' with a bound object for a non-const rvalue-reference argument:

void f(std::string &);
std::bind(&f, value());

Even though 'value()' yields an rvalue that cannot be bound by the non-const l-value reference of 'f', the fact that it is *copied* into the binder makes the above code legal. So what should it be? Since the generated code will cache, do we follow 'std::bind' way and accept the code? Or do we follow the somehow more intuitive path and fail to compile?

Going back to the basic syntax, I am not sure how we could take advantage of the possibility of using [&] vs. [=] for the capture (1). Remember that this is decided at the time that the function is defined, not at the caller's site, so we don't have really any context as of whether the caller wants to capture values, or references. That decision is taken away from the caller, although they can still achieve the same results by externally wrapping in a lambda:

void caller() {
   //...
   f([=]{ return a+b; }());    // captures copy
   f([&]{ return a+b; }());    // captures reference
   f(a+b);                        // whatever is decided if the feature is accepted
}

In the first two cases, the expression being passed is the evaluation of the lambda.

(1) Thought: Use [=] and [&] to determine whether the expression will be evaluated multiple times. Allowing 'void repeat(int count, void [&] arg)' and 'void single_evaluation(std::string const & [=] arg)'

There are other issues to be considered, like whether conversions from the result of the expression to the type spelled in the argument to the function should be done (most probably similar to 'std::function') and when (for a lambda that caches the value, should the conversion be done from the evaluation to the cache, or from the cache to the function?),  or what can be done inside the function with the argument that is captured by lambda:

void f(std::string [&] arg) {
    functors.emplace_back([=]{ return arg; });  // (a)
    functors.emplace_back([&]{ return arg; });  // (b)
}

I can see this proposal as being useful in some cases, but there are quite a bit of details that would have to be ironed out, and the end result has to be a feature that is natural and hardly confusing (well, with some meaning of 'confusing' in the context of the C++ standard...), or we risk opening a new can of worms by providing an easy to misuse feature.

    David

P.S. I really meant to write a short comment, sorry for the flood for those that believed the first paragraph.

Jean-Marc Bourguet

unread,
May 22, 2014, 12:03:52 PM5/22/14
to std-pr...@isocpp.org
Le mercredi 14 mai 2014 13:52:03 UTC-7, Hyman Rosen a écrit :
I propose to allow function parameter declarations to be prefixed with [&]

Two references which may interest some:

- Algol call by name is quite similar to this "pass-by-lambda" if the function is evaluated every time it is used.  If my memory is correct, that behavior was due to an error in the specification (and thus was not intended).  Yet it has been (ab)-used to pass anonymous functions (pass a reference to a variable, pass an expression using that variable, modify the variable, reevaluate the expression).  I see no interest in that as we have better and clearer way to achieve this.

- Functional language lazy evaluations is quite similar if the function result is memoized.  The major interest of lazy evaluation comes when you can put unevalated reference in a data structure (giving infinite data structure),  I think it would be interesting to see how far in that direction it is possible to go.

Yours,



Matthew Woehlke

unread,
May 22, 2014, 12:42:23 PM5/22/14
to std-pr...@isocpp.org
On 2014-05-22 01:59, Nicola Gigante wrote:
> Il giorno 21/mag/2014, alle ore 19:22, Matthew Woehlke ha scritto:
>> if the lambda is evaluated at all, it must be evaluated exactly
>> once.
>
> That's easy achievable by making sure that the wrapping lambda caches
> the result in a local variable the first time it is called.

I think it would be more performing to require that the compiler ensure
this at the use site. That way no extra code is needed if the lambda is
only ever used once anyway, or if the compiler is able to trivially
store the result in a local temporary without later having to check if
it has already done so or not. Else, if the compiler *does* need to add
guards, speculative branching can allow the lambda to be called
immediately, and the result thrown out if it was already called.
Otherwise you always have to wait on the call instruction before you can
continue.

That said, I'd be inclined to leave how this is enforced up to the
compiler writers, if feasible to do so.

>> I don't think there is an issue here; the type w/o '[&]' is the return
>> type of the lambda, which may have CV qualifiers as usual. All we're
>> doing is moving the execution point of the code from caller to callee.
>
> It can be not so simple, consider:
>
> void log(std::string const& message) {
> if(have_logs)
> std::cerr << message;
> }
>
> This is fine while this is not:
>
> void log(std::string const& [&] message) {
> if(have_logs)
> std::cerr << message;
> }
>
> This would mean that the implicit lambda is _returning_ a reference to a const string?

Yes.

> inside the lambda it would be in 90% of cases a local temporary, and
> you're returning a dangling reference, unless temporaries of the
> argument expression don't get different rules for their lifetime.

Hmm... normally this is not an issue as the lifetime of the parameter is
at least the lifetime of the function call. Maybe lazy parameters should
have an optional destructor?

Alternatively, the caller could execute an unconditional jump following
the call to an address that is initially set to bypass the cleanup code
as if the parameters were not delayed, which the "lambda" resets to call
the cleanup code.

Or reference types could simply be disallowed.

> If you allow "delayed" return values you should make sure local
> variables are captured by value (while the ones in the case of a
> function argument can and should be captured by reference).

...or like above, omit the clean-up code entirely and the return "value"
would be three instruction pointers; one to clean up already allocated
values if the return value is not used, one to clean up everything, and
of course one to evaluate the return value.

This likely would / should require that the delay is used only to short
circuit computation of a return value if the caller does not use it
(i.e. is not assigned to storage). That said... I know I've come across
cases where this is useful. (For example, the return iterator when
erasing from a container, if for some reason it is non-trivial to
compute the same, as the caller may not need it.)

> Now, if a function has a delayed return value and returns a delayed
> parameter, the parameter should not be evaluated, exactly as any
> other part of the returning expression:

Pedantic: if the delayed parameter is *only* involved in computing a
delayed return value, then yes.

> It has to be considered the fact that a function taking explicitly a
> lambda would be a template function, while if you allow such a delayed
> parameter in non-template functions then you have to implement the
> feature with a sort of type erasure for the lambda, which disable a lot
> of optimizations.

The way I envision this working is roughly like:

void log_info([&] string const& msg)
{
if (log_level >= INFO)
log(msg);
}

void foo()
{
log_info(complicated());
}

...which is transformed to:

void log_info(string const& ([[fastcall]] *_msg_ip)(void*),
void* _msg_pp)
{
if (log_level >= INFO)
{
// 'msg' only used once; no need to guard against multiple calls
log((*_msg_ip)(_msg_pp));
}
}

void foo()
{
struct __s
{
void* next;
char result[sizeof(string)];
};

[[fastcall]] string const& __i(void* p)
{
_p = (__s*)p;
_p->next = &_cleanup;
auto& s = new (_p->result) string(complicated());
return s;
}

__s __p = {&_end}; // reserve stack space
log_info(&__i, &__d, &__p);
goto *_.next;

_end:
return;

_cleanup:
(string*)(_.result)->~string();
goto _end;
}

(Hopefully I got that right; please excuse any errors.)

I'm not sure how that is significantly preventing optimizations? Except
for a couple of additional jumps, the code should be similar to what
would be generated for a non-delayed parameter.

(Note: if reference types are disallowed, the above is essentially the
same except without the placement new and cleanup instruction pointer.)

--
Matthew

Diggory Blake

unread,
May 22, 2014, 12:57:06 PM5/22/14
to std-pr...@isocpp.org


On Thursday, 22 May 2014 08:27:52 UTC+1, Nicola Gigante wrote:

Of course the compiler can do it, but I’m not sure it’s exactly the right approach. Usually, temporaries’ lifetime is that of their full expression, 
and they get destroyed in the reverse order of creation, right? It seems to me that this means that the caller has to run different cleanups for 
the case where the callee has evaluated the parameter and the case where it has not, which to me sounds weird and complex to implement.
It also adds complexity to the calling convention.

Rather than to state the temporaries’ lifetime in terms of what could have happened if the argument were not passed by lambda, I’d make 
them belong to the callee scope, which sounds more clear to me. Their lifetime could be that of local variables of the callee (like any other argument). This seems to me more natural, and that’s what I was suggesting in my last email, I’m sorry if I’ve not explained myself very 
clearly.

Bye,
Nicola

 I don't think it would be that strange, here's one way I see it possibly working:
- Space is reserved of the correct size and alignment for the parameter type (let's use "Foo"). This can either be directly on the stack, or as a field in the lambda (the advantage of the former being that the lambda would only need to capture the stack pointer, and so should always be able to take advantage of the small functor optimisation, regardless of "sizeof(Foo)", but aside from performance the two are equivalent.
- This space is left unconstructed when the lambda is constructed, but when the lambda is called for the first time, it evaluates the expression, and the result of the expression is constructed directly in this empty space, no copying or moving. This requires an extra boolean to track whether the lambda has been evaluated or not.
- The lambda returns the newly constructed object, either by reference or by value depending on the parameter qualifiers.
- In the lambda's destructor, it checks whether it was ever invoked, and if so explicitly calls the destructor on the constructed object.

Matthew Woehlke

unread,
May 22, 2014, 1:02:11 PM5/22/14
to std-pr...@isocpp.org
On 2014-05-22 11:18, David Rodríguez Ibeas wrote:
> Given:
>
> std::string value();
> void g(std::string [&] arg);
>
> I imagine that in this case the generated code would have to be the same,
> but return from the internal storage by value, which means that in the case
> of 'g(value())' two copies of the string would have to be taken.

Why would RVO not kick in here? I certainly expect for value() to return
a moved (RVO'd) string; why wouldn't the "lambda" also do so?

> (The generated code not knowing how many times it is going to be
> called would evaluate 'value()' and store it internally, the first
> evaluation would then have to create a second copy from the cache
> into the context of 'g').

This is a great reason why it should be guaranteed that the "lambda"
itself is only ever called once, i.e. the function taking a delayed
parameter is responsible for ensuring that. (Besides the potential
optimization when the compiler can trivially ensure this without a guard
flag.)

> void g(std::string [&] arg) {
> arg = "the new value";
> std::cout << arg; // This is not "the new value"!!!
> }

If this doesn't print "the new value", IMHO the implementation is broken.

> void h(std::string & [&] arg);
> h(value()); // compiler error?

Yes. IMO if you can't pass an expression through a regular parameter,
you can't through a delayed parameter either.

If the "lambda" doesn't need to do caching (IMO it shouldn't), these
issues don't exist. Delayed parameter evaluation should be exactly that;
*delayed evaluation*. Don't add additional logic (caching) to the logic
itself.

> Going back to the basic syntax, I am not sure how we could take advantage
> of the possibility of using [&] vs. [=] for the capture (1).

Maybe '[&]' is a bad syntax after all... maybe it should be
'[[delayed]]' (or '[[lazy]]', or what have you).

In fact, that might not be a bad thing... calling convention is
something of an issue, but at least you should get the same visible
behavior from a compiler that doesn't support delayed evaluation and
just silently does immediate evaluation. (For that matter, compilers
could provide both versions of such functions, which would allow code
built w/o delayed evaluation support to still link.)

> [The decision to capture by value or reference] is taken away from
> the caller, although they can still achieve the same results by
> externally wrapping in a lambda:

Why would you ever want to capture by value? The caller can neither
modify nor destroy the values itself while they are in scope of the
delayed parameter "lambda". Therefore I see no purpose to capturing by
value; at best, it just makes needless copies. (At worst, it prevents
the callee modifying the values in a way that is visible to the caller,
as would be the case for immediate evaluation.)

> (1) Thought: Use [=] and [&] to determine whether the expression will be
> evaluated multiple times. Allowing 'void repeat(int count, void [&] arg)'
> and 'void single_evaluation(std::string const & [=] arg)'

Or, I'd be okay with this :-). (But then the semantics of '[=]' should
still be that the callee ensures single call.)

--
Matthew

Diggory Blake

unread,
May 22, 2014, 1:04:39 PM5/22/14
to std-pr...@isocpp.org, dib...@ieee.org
On Thursday, 22 May 2014 16:18:20 UTC+1, David Rodríguez Ibeas wrote:

void g(std::string [&] arg) {
    arg = "the new value";
    std::cout << arg;                  // This is not "the new value"!!!
}


Personally I think it makes more sense for 'arg' to have the type of the lambda within the function, you should have to explicitly use the call operator to cause the lazy parameter to be evaluated. Assigning to it is then not an issue, as the implicitness is only in the calling code.

Matthew Woehlke

unread,
May 22, 2014, 1:21:03 PM5/22/14
to std-pr...@isocpp.org
On 2014-05-22 12:57, Diggory Blake wrote:
> Here's one way I see it possibly working:
>
> - Space is reserved of the correct size and alignment for the parameter
> type (let's use "Foo"). This can either be directly on the stack, or as a
> field in the lambda (the advantage of the former being that the lambda
> would only need to capture the stack pointer, and so should always be able
> to take advantage of the small functor optimisation, regardless of
> "sizeof(Foo)", but aside from performance the two are equivalent.
>
> - This space is left unconstructed when the lambda is constructed, but when
> the lambda is called for the first time, it evaluates the expression, and
> the result of the expression is constructed directly in this empty space,
> no copying or moving. This requires an extra boolean to track whether the
> lambda has been evaluated or not.
>
> - The lambda returns the newly constructed object, either by reference or
> by value depending on the parameter qualifiers.
>
> - In the lambda's destructor, it checks whether it was ever invoked, and if
> so explicitly calls the destructor on the constructed object.

This is basically what I was thinking, but with the minor change that,
rather than store a bool, the "lambda" ("delayed evaluation context"
might be more accurate) stores the instruction pointer where to pick up
after the call dispatches. I'm not actually sure which is more
efficient, and anyway probably this should be an implementation detail
(I don't believe it matters to the callee how the caller implements
clean-up).

--
Matthew

jgot...@gmail.com

unread,
May 22, 2014, 10:45:08 PM5/22/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net


On Wednesday, May 21, 2014 1:22:05 PM UTC-4, Matthew Woehlke wrote:
On 2014-05-14 18:23, David Rodríguez Ibeas wrote:


> From a user point of view, I am not too fond of the idea of the lambda
> being evaluated on each use inside the function.

I must strongly agree with this; if the lambda is evaluated at all, it
must be evaluated exactly once. In any other direction lies madness.

 
What happens in the following case?

void foo([&] int x);  //Defined in another translation unit

void bar ([&] int x){
    foo
(x);
}


 Is x evaluated in foo?  It depends on whether x is evaluated in bar.  Is there any way for foo to know whether it is or isn't? If not, is this a problem?

Joe Gottman

Diggory Blake

unread,
May 23, 2014, 5:58:13 AM5/23/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net, jgot...@gmail.com
It's not a problem, the caller of 'foo' doesn't need to know if the expression is evaluated, it just has to wrap up the expression in a lambda, and then 'bar' decides whether or not to invoke it at runtime. The part about only evaluating it at most once would be a runtime check (modulo any optimisations the compiler might be able to do).

David Rodríguez Ibeas

unread,
May 23, 2014, 10:22:47 AM5/23/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net, jgot...@gmail.com
Even if the forwarding of the functor argument did not involve wrapping in another mechanism, we still need a boolean flag to determine whether we need to cleanup or not (i.e. if it has been evaluated, the destructor needs to be run over the created object). That boolean literally means: I have already evaluated the expression, and can be used to avoid the evaluation.

The cost would be an additional branch in each function: where before we could assume that the argument was unevaluated externally, the forwarding case breaks that assumption. This should be an implementation detail, as there are tradeoffs in both approaches. The one Diggory presented requires an extra bool in memory and potentially an additional indirection to call the expression, this approach doesn't, but every use pays for an extra branch for the case where it might have been forwarded.


--

Diggory Blake

unread,
May 23, 2014, 10:48:40 AM5/23/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net, jgot...@gmail.com, dib...@ieee.org
It would be possible to offer the choice of whether or not an indirection is necessary with syntax like the following.

With indirection, but allowing implementation to be hidden:
void foo([[lazy]] std::function<string ()> arg) {
    cout
<< arg();
}

Without indirection (in this case arg() might not return a string, but that can be enforced easily enough when concepts are implemented):
template<typename T>
void bar([[lazy]] T const& arg) {
    cout
<< arg();
}

In both cases the compiler would generate a functor at the call-site. In the first case this would be wrapped in a std::function, in the second case it would be inferred as the type T.

Matthew Woehlke

unread,
May 23, 2014, 11:42:50 AM5/23/14
to std-pr...@isocpp.org
On 2014-05-22 22:45, jgot...@gmail.com wrote:
> What happens in the following case?
>
> void foo([&] int x); //Defined in another translation unit
>
> void bar ([&] int x){
> foo(x);
> }
>
> Is x evaluated in foo? It depends on whether x is evaluated in bar. Is
> there any way for foo to know whether it is or isn't? If not, is this a
> problem?

In this *specific* case (i.e. 'x' of same type and not otherwise used) I
would suspect/hope that the compiler can simply forward the delayed
expression to foo().

Otherwise, I'd say the compiler should generate a new delayed evaluation
context inside bar() that would either simply return an
already-evaluated 'x' (if it has already been evaluated), or have the
necessary logic to test if it has not been evaluated, do so (and mark it
so, storing the result locally for later use if applicable), and return
the result.

--
Matthew

Hyman Rosen

unread,
May 23, 2014, 1:38:16 PM5/23/14
to std-pr...@isocpp.org
I haven't chimed in since my original post.  I would just like to explain/summarize my understanding of the salient points of the proposal.

1)  The annotation [&] of the parameter is a mnemonic to indicate that evaluation of the parameter causes the side effects of the argument expression when it is evaluated.  I don't anticipate needing other capture forms.

2) I agree that the 0-or-1 evaluation model is better and easier to understand than the multiple-evaluation model, and withdraw the latter.

3) For each [&] parameter, the called function contains reserved space on the stack for the parameter, plus a boolean variable indicating whether the argument expression has been evaluated. [User-accessible? Maybe!]  Upon exit from the function, if the boolean variable is set, the destructor for the parameter is called.

4) The first evaluation of the parameter in any context (even just having its address taken, for example, but not in non-evaluated contexts such as sizeof) causes the argument to be evaluated as if it had been an ordinary function call argument and results in the reserved parameter space being initialized as if it had been an ordinary parameter.

5) The "lambda" (or "thunk", to bring back an old term) used for this could be a pointer to something as simple as
    struct { void (*f)(void *this_struct, void *parameter); };
The caller creates one of these objects on the stack with f pointing to the argument evaluation code and passes a pointer to it to the callee, and the callee calls this function passing back the pointer and the address of the parameter to initialize.  Any extra data needed is placed immediately after this struct (which is why the callee passes the pointer back).  There's no need for the called function to be a template.  There's no need for the thunk to be representable as an ordinary lambda.


Diggory Blake

unread,
May 23, 2014, 2:13:22 PM5/23/14
to std-pr...@isocpp.org


On Friday, 23 May 2014 18:38:16 UTC+1, Hyman Rosen wrote:
I haven't chimed in since my original post.  I would just like to explain/summarize my understanding of the salient points of the proposal.

1)  The annotation [&] of the parameter is a mnemonic to indicate that evaluation of the parameter causes the side effects of the argument expression when it is evaluated.  I don't anticipate needing other capture forms.

2) I agree that the 0-or-1 evaluation model is better and easier to understand than the multiple-evaluation model, and withdraw the latter.

3) For each [&] parameter, the called function contains reserved space on the stack for the parameter, plus a boolean variable indicating whether the argument expression has been evaluated. [User-accessible? Maybe!]  Upon exit from the function, if the boolean variable is set, the destructor for the parameter is called.

4) The first evaluation of the parameter in any context (even just having its address taken, for example, but not in non-evaluated contexts such as sizeof) causes the argument to be evaluated as if it had been an ordinary function call argument and results in the reserved parameter space being initialized as if it had been an ordinary parameter.

So you're suggesting having the parameter be evaluated implicitly just by using the name?
- C++ has so far stayed away from having "hidden" method calls in the code. Up until now it's always been clear what is a function call and might go off and execute some other code, with this it wouldn't be.
- What do you expect the behaviour of this to be:
auto foo(string [&] arg) {
   
return [=]() { return arg; };
}

- What about this:
auto foo(string [&] arg) {
   
auto unused = arg;
   
return [=]() { return arg; };
}

- Or this:
foo(1/0)
void foo(int [&] arg) {
   
int a = arg; // Divide by zero???
}

IMHO, having the evaluation be implicit is a bad idea. It should be clear to anyone reading the code that it's going off and evaluating some other expression.

 

5) The "lambda" (or "thunk", to bring back an old term) used for this could be a pointer to something as simple as
    struct { void (*f)(void *this_struct, void *parameter); };
The caller creates one of these objects on the stack with f pointing to the argument evaluation code and passes a pointer to it to the callee, and the callee calls this function passing back the pointer and the address of the parameter to initialize.  Any extra data needed is placed immediately after this struct (which is why the callee passes the pointer back).  There's no need for the called function to be a template.  There's no need for the thunk to be representable as an ordinary lambda.


Lambdas are just anonymous structs like that, and the type erasure you are describing is exactly the same as that done by std::function already. Seems silly to duplicate the functionality. Templates would avoid the need for the extra indirection, so it would be nice if it were possible to use them.

Nicola Gigante

unread,
May 23, 2014, 2:23:48 PM5/23/14
to std-pr...@isocpp.org, Nicola Gigante

Il giorno 23/mag/2014, alle ore 19:37, Hyman Rosen <hyman...@gmail.com> ha scritto:

> I haven't chimed in since my original post. I would just like to explain/summarize my understanding of the salient points of the proposal.
>
> 1) The annotation [&] of the parameter is a mnemonic to indicate that evaluation of the parameter causes the side effects of the argument expression when it is evaluated. I don't anticipate needing other capture forms.
>
> 2) I agree that the 0-or-1 evaluation model is better and easier to understand than the multiple-evaluation model, and withdraw the latter.
>
> 3) For each [&] parameter, the called function contains reserved space on the stack for the parameter, plus a boolean variable indicating whether the argument expression has been evaluated. [User-accessible? Maybe!] Upon exit from the function, if the boolean variable is set, the destructor for the parameter is called.
>
> 4) The first evaluation of the parameter in any context (even just having its address taken, for example, but not in non-evaluated contexts such as sizeof) causes the argument to be evaluated as if it had been an ordinary function call argument and results in the reserved parameter space being initialized as if it had been an ordinary parameter.
>
> 5) The "lambda" (or "thunk", to bring back an old term) used for this could be a pointer to something as simple as
> struct { void (*f)(void *this_struct, void *parameter); };
> The caller creates one of these objects on the stack with f pointing to the argument evaluation code and passes a pointer to it to the callee, and the callee calls this function passing back the pointer and the address of the parameter to initialize. Any extra data needed is placed immediately after this struct (which is why the callee passes the pointer back). There's no need for the called function to be a template. There's no need for the thunk to be representable as an ordinary lambda.
>

A great summary!

I think the interest and the consensus over this proposal are relatively high, as well
as its feasibility.
I have some questions in my head:
1) is there a good way to specify and implement this in a way
that such a function can be linkable from code unaware of this new feature?
2) syntax: If we don't talk about captures, I don't like [&] very much. As
someone else has suggested, what about an attribute?

void foo([[lazy]] int n); // Although I don't know if an attribute can appear in that position
or
void foo(lazy int n); // context-sensitive keyword like final and override

3) can this be generalized to class members? Infinite data structures are a strong
selling point of lazy languages like Haskell. Maybe we can find a way to support this feature.
What could happen if the language allowed the following?

template<typename T>
struct List {
T _head;
lazy List<T> _tail; // A member of type T inside of type T itself: allowed because it's delayed

List(T head, lazy List<T> const& tail) : _head(head), _tail(tail) { }
};

I would say that the member could be represented in a way similar to
the case of function parameters, and the first access to the member would
result in an evaluation.

What do you think?

Bye,
Nicola


Matthew Woehlke

unread,
May 23, 2014, 2:39:22 PM5/23/14
to std-pr...@isocpp.org
On 2014-05-23 13:37, Hyman Rosen wrote:
> 3) For each [&] parameter, the called function contains reserved space on
> the stack for the parameter, plus a boolean variable indicating whether the
> argument expression has been evaluated.

Others may have expressed that desire. I still prefer that the callee
reserves space, but the caller is responsible for /ensuring/ (as an
implementation detail) that the lambda is only called once. The
mechanism for doing this shall not be specified. I think it's important
that the compiler be permitted to make this assurance simply by knowing
that no code path results in multiple evaluation, in which case no flag
would be needed.

> [User-accessible? Maybe!]

Please, no. :-) See also next comment.

> Upon exit from the function, if the boolean variable is set, the
> destructor for the parameter is called.

This requires that a clean-up code pointer is also passed to the callee.
That's an acceptable option, though as above, the callee should not be
required to use a flag if the compiler is able to determine that one is
not needed.

Another option is for the evaluation cope pointer to set a jump address
in the caller's stack context, such that on return, the caller
unconditionally jumps to this address (by default, the address would be
set to skip clean-up). Another advantage to this option that I just
realized is that, in the case of a single delayed parameter¹, this
"return address" can be the usual return address register, which
eliminates even the additional jump.

(¹ Actually, the first delayed parameter can always do this, and even
subsequent ones might be able to do so iff they are the first parameter
evaluated. Again... there are optimization possibilities, so how to
implement this is best left to the compilers.)

> 5) The "lambda" (or "thunk", to bring back an old term)

Heh. I was thinking about using "thunk" in the above :-). Given that I
don't see these being "lambdas" in the sense that "lambda" is a specific
object type in C++, it might be better to not call them such.

> used for this could be a pointer to something as simple as
> struct { void (*f)(void *this_struct, void *parameter); };

Something like that, yes.

Basically, 2-3 things are needed; an instruction address to the thunk,
which should use some form of "fast" calling convention (i.e. if it
needs a full stack frame, it must set it up itself; hopefully most
*won't*) and takes a single parameter, which is a void pointer to data
space that the caller passes in. The meaning of the data pointer shall
be unspecified and left up to the caller/thunk, e.g. it might be a
pointer into the caller's stack, or a direct pointer to some class
member that is manipulated (e.g. '&i' in case of trivial thunks like '++i').

The third is an instruction addresss to clean-up code, if the callee is
responsible for calling the same.

> The caller creates one of these objects on the stack

I wouldn't require this... especially if the caller handles clean-up,
we're talking about only two pointers. For a callee taking a single
parameter, it makes no sense not to just pass these in registers. (IOW,
leave it as an implementation detail how the necessary pointers are
passed to the callee.)

> and the callee calls this function passing back the pointer and the
> address of the parameter to initialize.

Why wouldn't the thunk return the result using the normal conventions
for doing so?

I could *possibly* see an argument for this. However, a major argument
*against* is that it precludes returning the result in a register, in
case the result is something simple (e.g. 'int'). Why require allocating
stack space for the result if not necessary?

> Any extra data needed is placed immediately after this struct (which
> is why the callee passes the pointer back).

Right; the 'data pointer' component is opaque to the callee and
references whatever the thunk needs to execute.

> There's no need for the called function to be a template. There's no
> need for the thunk to be representable as an ordinary lambda.

*Right*. I feel strongly on this point; trying to do either of these
makes the implementation less performing and reduces potential for
optimization, for no obvious benefit.

--
Matthew

Matthew Woehlke

unread,
May 23, 2014, 2:47:41 PM5/23/14
to std-pr...@isocpp.org
On 2014-05-23 14:13, Diggory Blake wrote:
> On Friday, 23 May 2014 18:38:16 UTC+1, Hyman Rosen wrote:
>> 4) The first evaluation of the parameter in any context (even just having
>> its address taken, for example, but not in non-evaluated contexts such as
>> sizeof) causes the argument to be evaluated as if it had been an ordinary
>> function call argument and results in the reserved parameter space being
>> initialized as if it had been an ordinary parameter.
>
> So you're suggesting having the parameter be evaluated implicitly just by
> using the name?

What do you mean by "using the name"? If you need the value (or address)
of the parameter, then yes, it must be evaluated at that point.

> - C++ has so far stayed away from having "hidden" method calls in the code.
> Up until now it's always been clear what is a function call and might go
> off and execute some other code, with this it wouldn't be.

If this is just a matter of requiring that one write 'arg()' to get the
value of a delayed parameter, I could live with that.

> - What do you expect the behaviour of this to be:
> auto foo(string [&] arg) {
> return [=]() { return arg; };
> }

Since you are binding by value, defining the lambda requires the value
of 'arg'. Therefore, your example is equivalent to:

auto foo(string [&] arg) {
auto const string __arg = arg;
return [=]() { return __arg; };
}

(Note that there is no flag in the above; the compiler knows that 'arg'
is only ever evaluated once, and so does not need to generate such code.)

> - What about this:
> auto foo(string [&] arg) {
> auto unused = arg;
> return [=]() { return arg; };
> }

Same as above, for the same reason.

> - Or this:
> foo(1/0)
> void foo(int [&] arg) {
> int a = arg; // Divide by zero???
> }

Maybe not. Since 'a' is not used, I could imagine that the compiler
generates an empty body for foo().

This suggests that perhaps naming the argument forces evaluation, even
if the value is subsequently not used. In which case, yes, the above
would divide by zero. (It would for an immediate parameter, also; we've
only changed at what point the exception is generated.)

--
Matthew

Matthew Woehlke

unread,
May 23, 2014, 2:55:06 PM5/23/14
to std-pr...@isocpp.org
On 2014-05-23 14:23, Nicola Gigante wrote:
> I have some questions in my head:
> 1) is there a good way to specify and implement this in a way
> that such a function can be linkable from code unaware of this new feature?

I would expect it is trivial to have the compiler generate two forms for
a symbol, one with delayed paramaters, and one in which the parameters
are instead immediate, such that "old" compilers would be able to call
the immediate-parameters version. (I already suggested this where I
mentioned the possibility of using an attribute to denote delayed
evaluation rather than '[&]', which would also allow "old" compilers to
seamlessly¹ build code that otherwise uses this feature.)

I don't want to attempt to have "old" compilers able to generate code
that would ("correctly") call a function that takes delayed parameters.

(¹ ...but *ONLY* if the parameters are evaluated by being named; if we
make them look like functors instead, this falls apart.)

--
Matthew

David Rodríguez Ibeas

unread,
May 23, 2014, 2:55:36 PM5/23/14
to std-pr...@isocpp.org
Matthrew, yan you review this paragraph?

On Fri, May 23, 2014 at 2:39 PM, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:
Others may have expressed that desire. I still prefer that the callee
reserves space, but the caller is responsible for /ensuring/ (as an
implementation detail) that the lambda is only called once. The
mechanism for doing this shall not be specified. I think it's important
that the compiler be permitted to make this assurance simply by knowing
that no code path results in multiple evaluation, in which case no flag
would be needed.

I am not sure whether you really mean what you wrote, it seems to me that the 'caller' cannot possibly ensure that the argument is evaluated at most once, right?

David Rodríguez Ibeas

unread,
May 23, 2014, 3:09:45 PM5/23/14
to std-pr...@isocpp.org
Sorry for the typo: Matthew. Ouch

Matthew Woehlke

unread,
May 23, 2014, 4:32:37 PM5/23/14
to std-pr...@isocpp.org
On 2014-05-23 14:55, David Rodríguez Ibeas wrote:
> On Fri, May 23, 2014 at 2:39 PM, Matthew Woehlke wrote:
>> Others may have expressed that desire. I still prefer that the callee
>> reserves space, but the caller is responsible for /ensuring/ (as an
>> implementation detail) that the lambda is only called once. The
>> mechanism for doing this shall not be specified. I think it's important
>> that the compiler be permitted to make this assurance simply by knowing
>> that no code path results in multiple evaluation, in which case no flag
>> would be needed.
>
> I am not sure whether you really mean what you wrote, it seems to me that
> the 'caller' cannot possibly ensure that the argument is evaluated at most
> once, right?

Oops, yes... good catch; thanks. Too many caller / callee to keep
straight :-).

(That should be "I still prefer that the *caller* reserves space, but
the *callee* is responsible for ensuring [...] that the [thunk] is only
called once.")

--
Matthew

Diggory Blake

unread,
May 23, 2014, 4:48:42 PM5/23/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
I'm not sure it's a good thing for old compilers to be able to compile it but just ignore the lazy part of the evaluation. Lazy evaluation changes the behaviour of the program - a valid and useful program which relies on lazy evaluation may not even terminate if parameters are evaluated early. If code using lazy arguments is compiled on a compiler without support for them it should error.

Mikhail Semenov

unread,
May 25, 2014, 2:43:09 PM5/25/14
to std-pr...@isocpp.org

The idea is quite simple if we look at the definition of the proposal in terms of transformation.
If we have a function defined as follows:
void f([&] T x)
{
   
if (b)
      a
= x();
}



and is used like this:
f(e);


Then it is equivalent to the following program fragment:
The function f1 is defined as follows:
void f1(function<T()> expr)
{
   
if (b)
      a
= expr();
}


and is used as follows:

f1([&]()->T { return e;});


We can have a similar definition for other cases, g([=] T x), etc.

By the way it looks obvious that we may have even the case:
void p([&] void x)
{
   
if (b)
       x
(); // we are only interested in side effects, no need of the value.
}



It is good that we keep () after the parameter it makes it clear that it is a function call.

Edward Catmur

unread,
May 25, 2014, 4:13:11 PM5/25/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
I wonder if it might be a better idea to implement pass-by-lambda with a special library type (class template), along the lines of std::initializer_list.  Compared to a new parameter syntax this has the advantage that no changes to core syntax or ABI are required, which is good not only for compilers but also for tools that parse source code, analyze symbols etc.

Using std::lazy_expression<T> as a tentative name for this class template, the requirements are:

* Deleted default constructor; deleted copy and move constructors and assignment operators (preventing such an object leaking out of the callee)
* Default destructor
* Exposition-only data members unspecified<T()> expression, std::optional<T> value

For purposes of overload resolution, lazy_expression is considered to have a non-explicit converting constructor lazy_expression(T) noexcept; however the corresponding argument is not evaluated in place but instead captured as though by a lambda-expression [&]() -> T { return e; }; the closure object resulting from evaluation of the lambda-expression is used to initialize the data member expression. [Note: the noexcept requirement implies that the constructor of expression shall not perform memory allocation. --end note]

For access to the lazy-initialized value, I think std::reference_wrapper<T> is a good model as it balances implicit use via conversion operator with an explicit get() member for those who want to be explicit or need to e.g. access members of T through the dot operator:

* Conversion function operator T& () { if (!value) value = expression(); return *value; }
* Member function T& get() with the same behavior
* Invocation function call template operator()(ArgTypes&&...)
* Member types (again std::reference_wrapper is a good model here)

One emergent feature of this proposal is that (again as with std::initializer_list) it is possible to construct a std::lazy_expression<T> as a local variable, not only as a function parameter. On balance I think this is a feature as I can think of cases where this would simplify code (the same complex sub-expression appearing in multiple branches).

David Krauss

unread,
May 26, 2014, 12:04:49 AM5/26/14
to std-pr...@isocpp.org
Another approach would be to pass a lambda (or equivalent entity like my proposed inline variable) directly, and then let the user idiomatically wrap this in something else according to desired semantics. The standard may define the wrappers generically, e.g. std::one_shot_functor which can only be called as an rvalue, std::memoized_functor which caches a return value and returns it on subsequent calls, etc. Without wrapping, the user gets multiple evaluation and repeated side effects.

Dynamic dispatching wrappers may enable definition of non-inline lazy functions. But the user would still have to define the inline trampoline function to wrap the naked lambda.

Although functional-style memoization may be the nicest semantic, I think it’s too complicated and too unlike the way the virtual machine generally operates to define very well in core language terms. std::initializer_list is a cautionary tale about meshing the core language and library with nontrivial semantics… it even seems like it should be trivial!

Moreover the overhead of memoization is too high to give the user no alternative.

David Krauss

unread,
May 26, 2014, 12:07:52 AM5/26/14
to std-pr...@isocpp.org

On 2014–05–26, at 12:04 PM, David Krauss <pot...@gmail.com> wrote:

> Dynamic dispatching wrappers may enable definition of non-inline lazy functions. But the user would still have to define the inline trampoline function to wrap the naked lambda.

Well, I suppose it could be done by the caller, by an implicit conversion constructor of the wrapper. Such use of implicit conversions needs to be studied carefully, but this might be appropriate.

Mikhail Semenov

unread,
May 26, 2014, 5:52:47 AM5/26/14
to std-pr...@isocpp.org

There is no problem with multiple evaluations of the lambda parameter. In some way, the proposed mechanism is similar to one of the uses of the Lisp eval: you pass an expression as a parameter and then evaluate it inside the function.  You can evaluate it many times. it can be used for multiple evaluations, and inside the function it is clear what the code is doing. But when you call a function it may not be clear at all.

x = 0;
f(y/x); // but if it is used as a lambda parameter, it may be changed inside:

int f([&] int expr)
{
    int sum = 0;
    for (x = 1; x <= 10; x++)
    {
        sum += expr();
    }
    return sum;
};

Matthew Woehlke

unread,
May 26, 2014, 1:28:13 PM5/26/14
to std-pr...@isocpp.org
On 2014-05-25 16:13, Edward Catmur wrote:
> I wonder if it might be a better idea to implement pass-by-lambda with a
> special library type (class template), along the lines of
> std::initializer_list. Compared to a new parameter syntax this has the
> advantage that no changes to core syntax or ABI are required, which is good
> not only for compilers but also for tools that parse source code, analyze
> symbols etc.

If we used instead '[[delayed]]' (or '[[lazy]]', but TBH I like
'delayed' better), and don't apply the need to use ()'s when using such
parameter, then code tools don't need to change *at all* (assuming they
already recognize generic attributes, and don't need to do anything
special for delayed parameters).

As a further (albeit admittedly debatable) bonus, compilers that don't
support this feature will still compile the code.

I don't see this working very well as only a library-level feature. At
best, the compiler will optimize it at the language level anyway. At
worst, you'll lose all sorts of opportunity for optimization, which is
rather counter to the objective of the feature.

> One emergent feature of this proposal is that (again as with
> std::initializer_list) it is possible to construct a
> std::lazy_expression<T> as a local variable, not only as a function
> parameter.

Why would a language-based implementation preclude this possibility?

[[delayed]] auto x = complicated_expr(); // what's wrong with this?

This would have the same semantics as a delayed parameter; 'x' is
initialized on first use. Again, doing this at the *language level*
allows for much better optimization. For example:

[[delayed]] auto x = complicated_expr();
if (cond)
{
do_something(x);
...
}
else if (cond)
{
...stuff not using 'x'...
}
else
{
do_something_else(x)
...
}

...can be transformed by the compiler to:

if (cond)
{
do_something(complicated_expr());
... // assume this didn't use 'x'
}
else if (cond)
{
...
}
else
{
auto x = complicated_expr();
do_something_else(x)
... // e.g. this does use 'x'
}

Note that in this case there is *ZERO* overhead to using [[delayed]];
basically the feature has allowed us to move an otherwise-duplicated
expression up out of a conditional tree, but still elide the code to
initialize the variable in case of the branch where it isn't used.

I'd like to see that same zero overhead (both in instructions and stack
usage) with a library level feature.



The big "gotcha" in both cases is when the expression has observable
side effects. Though I would argue that (except in case of implementing
proper short-circuiting Boolean operators) one should look at the
feature as a performance optimization, and generally avoid such cases.
(Or else let it be on the users' heads to take necessary steps to ensure
that all compilers in use support the feature.)

--
Matthew

Mikhail Semenov

unread,
May 26, 2014, 2:13:17 PM5/26/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net

Why would a language-based implementation preclude this possibility?

   [[delayed]] auto x = complicated_expr(); // what's wrong with this?

Are you suggesting that we can define a parameter:

void f([[delayed]] T x)
{
    if(cond)
          send(x); // evaluates x;
}

I think the problem with "lazy evaluation" is that it is not obvious when the variable is going to be evaluated. It is not a clear syntax.
My understanding is that it is much clearer to write:

void f([&] T x) // the parameter specification is OK
{
    if(cond)
          send(x()); // evaluates x; the syntax is clearer here
}

It's interesting that Algol was mentioned. It was in Algol-60 that introduced a function parameter that could pick up any expression as an actual parameter, which could be evaluated inside a function body. But I am not aware of any implementation that used that feature.



Ville Voutilainen

unread,
May 26, 2014, 2:31:30 PM5/26/14
to std-pr...@isocpp.org
On 26 May 2014 20:27, Matthew Woehlke <mw_t...@users.sourceforge.net> wrote:
> Why would a language-based implementation preclude this possibility?
>
> [[delayed]] auto x = complicated_expr(); // what's wrong with this?
>
> This would have the same semantics as a delayed parameter; 'x' is
> initialized on first use. Again, doing this at the *language level*
> allows for much better optimization. For example:

You want that to have semantics? Then get rid of the attribute and replace it
with something else.

Edward Catmur

unread,
May 26, 2014, 2:41:33 PM5/26/14
to std-pr...@isocpp.org
Other issues with an attribute:
* new ABI (need some way to mark parameters as delayed) 
* how do you take a pointer to a function with a delayed parameter (it needs to appear in the function pointer type)

It seems to me that delayed parameters are going to leak out into the type system, which means that it's best for them to be part of the type system to start with. I suppose a new reference class could do the job, but I'd be wary of something that powerful.

Matthew Woehlke

unread,
May 26, 2014, 2:45:39 PM5/26/14
to std-pr...@isocpp.org
Again, I'm looking at this *mostly* from the perspective of
optimization. IOW, '[[delayed]] x' and the substitution of 'expr' for x
(be it assignment or parameter passing) is a promise to the compiler
that neither the construction or destruction of 'x', nor the evaluation
of 'expr', has side effects, and that the order in which those
operations are performed (or indeed, if they are performed at all) may
be modified accordingly. In that sense, the level of semantic meaning is
similar to [[likely]].

(For that mapper, OMP attributes aren't exactly semantic free either,
especially if the code in an OMP loop has any degree of thread awareness
or execution order dependency.)

Again, the main benefit to using an attribute is that it is transparent
to code tools and even old compilers... code using it will still
*compile* on a compiler that doesn't support the feature. (It may be
wrong, but then, I could also write OMP code that is wrong - or at least
behaves differently - when build w/o OMP support.)

If that possibility is unacceptable, than some other syntax (e.g. '[=]',
or adding a keyword) can be used, but then code tools must learn about
the feature in order to still work. (Conversely, it's getting
increasingly impractical to parse C++ with anything short of a
full-blown compiler, e.g. libclang, which may substantially mitigate
that cost.)

--
Matthew

Matthew Woehlke

unread,
May 26, 2014, 2:50:05 PM5/26/14
to std-pr...@isocpp.org
On 2014-05-26 14:41, Edward Catmur wrote:
> Other issues with an attribute:
> * new ABI (need some way to mark parameters as delayed)
> * how do you take a pointer to a function with a delayed parameter (it
> needs to appear in the function pointer type)

I fail to see what either of these have to do with using an attribute
vs. some other decoration. The ABI of a delayed vs. immediate parameter
is different. That's just a given. (In fact it's true even using a
library-only implementation, except that there is no compiler change
needed since 'int' vs. e.g. 'std::delayed<int>' are already different
types.)

--
Matthew

Matthew Woehlke

unread,
May 26, 2014, 2:55:06 PM5/26/14
to std-pr...@isocpp.org
On 2014-05-26 14:13, Mikhail Semenov wrote:
> Are you suggesting that we can define a parameter:
>
> void f([[delayed]] T x)
> {
> if(cond)
> send(x); // evaluates x;
> }

Yes. Note however that the reasons to do so are more for backwards
compatibility. That said...

> I think the problem with "lazy evaluation" is that it is not obvious when
> the variable is going to be evaluated. It is not a clear syntax.
> My understanding is that it is much clearer to write:
>
> void f([&] T x) // the parameter specification is OK
> {
> if(cond)
> send(x()); // evaluates x; the syntax is clearer here
> }

...it's not necessarily more obvious that 'x' will be evaluated here
than the above. If send() also uses [[delayed]] on the parameter, then
there is still no evaluation occurring (yet). (Furthermore, one hopes
that the compiler is able - in some cases anyway - to avoid generating a
second thunk here and rather just pass along the thunk that was passed in.)

I also don't entirely buy the importance of knowing in the *callee*
where evaluation happens. Especially as the callee has no idea what (if,
indeed, anything at all) "evaluation" entails. In the callee, all the
user should need to know is that the caller's thunk is evaluated if the
parameter is "used".

Delayed evaluation is of *much* more interest to the *caller*, which
cannot know at what point, or even if, the expression is evaluated.
However I see this being exactly as a big (or not) of an issue as
existing short circuit evaluation.

--
Matthew

Edward Catmur

unread,
May 26, 2014, 3:02:49 PM5/26/14
to std-pr...@isocpp.org

On 26 May 2014 19:50, "Matthew Woehlke" <mw_t...@users.sourceforge.net> wrote:
> I fail to see what either of these have to do with using an attribute
> vs. some other decoration. The ABI of a delayed vs. immediate parameter
> is different. That's just a given. (In fact it's true even using a
> library-only implementation, except that there is no compiler change
> needed since 'int' vs. e.g. 'std::delayed<int>' are already different
> types.)

If you need the attribute to form a function pointer, then how can that be deduced by a template? I worry that semantic attributes will produce a category of second-class functions that can't be metaprogrammed correctly.

Edward Catmur

unread,
May 26, 2014, 3:14:03 PM5/26/14
to std-pr...@isocpp.org


On 26 May 2014 05:04, "David Krauss" <pot...@gmail.com> wrote:
>
> Another approach would be to pass a lambda (or equivalent entity like my proposed inline variable) directly, and then let the user idiomatically wrap this in something else according to desired semantics. The standard may define the wrappers generically, e.g. std::one_shot_functor which can only be called as an rvalue, std::memoized_functor which caches a return value and returns it on subsequent calls, etc. Without wrapping, the user gets multiple evaluation and repeated side effects.

I'm inclined to agree; SRP demands that memoization be orthogonal to other aspects of the proposal. I'd still like a way to assure the caller that memoization occurs so that the expression is evaluated at most once.

It does seem that separating type erasure from implicit expression would be a good idea as well, though I'm not sure how that would work. You'd need a way to specify that a parameter is deduced as an expression capture, which implies a way to write that in the type system.

Mikhail Semenov

unread,
May 27, 2014, 4:36:27 AM5/27/14
to std-pr...@isocpp.org, eca...@googlemail.com

I'm inclined to agree; SRP demands that memoization be orthogonal to other aspects of the proposal. I'd still like a way to assure the caller that memoization occurs so that the expression is evaluated at most once.

It does seem that separating type erasure from implicit expression would be a good idea as well, though I'm not sure how that would work. You'd need a way to specify that a parameter is deduced as an expression capture, which implies a way to write that in the type system.

 The point is that "passing-by-lambda" parameter mechanism (as initially proposed) is rather clear and allows the user to control the evaluation in the callee, whereas lazy-evaluation involves unclear definition of when the parameter is going to be evaluated. I don't see a problem with multiple evaluations of the same parameter. If you don't want to evaluate it many times, don't do it: assign it to a variable.
 
The passing-by-lambda mechanism is very clear: just allow the compiler to transform an expression into a lambda-expression in the caller: all the necessary information is available anyway.  The callee picks up this lambda expression and uses it as a function: that's all. There are know sematic or syntactic pitfalls. The only thing the implementation has to do is to pass the lambda expression as a parameter efficiently.
 

Edward Catmur

unread,
May 27, 2014, 6:55:51 AM5/27/14
to std-pr...@isocpp.org, eca...@googlemail.com
On Tuesday, 27 May 2014 09:36:27 UTC+1, Mikhail Semenov wrote:
The passing-by-lambda mechanism is very clear: just allow the compiler to transform an expression into a lambda-expression in the caller: all the necessary information is available anyway.  The callee picks up this lambda expression and uses it as a function: that's all. There are know sematic or syntactic pitfalls. The only thing the implementation has to do is to pass the lambda expression as a parameter efficiently.
 
Closure types are unique, which would imply that a pass-by-lambda function would have to be templated on the closure type of its pass-by-lambda parameter.  If the closure type is erased, then we need a type to perform the erasure efficiently, avoiding memory allocation and as transparent as possible to optimization, which precludes the use of std::function<T()>. This type could be hidden behind a special syntax, but exposing it allows pass-by-lambda functions to be treated normally by existing metaprogramming facilities.

I'm leaning towards a 3-level solution:
  1. Template syntax to specify that a deduced functor parameter performs implicit lambda capture
  2. Efficient, non-allocating type-erased wrapper templated on the result type
  3. Evaluation-semantic wrappers (for memoization etc.)
Strawman for level 1:

template<typename F>
std
::enable_if_t<std::is_convertible_v<std::result_of_t<F()>, int>, int>
passed_by_lambda
([&] F f) {
 
return f();
}

Note that any instantiation of passed_by_lambda will necessarily have F the type of the implicitly-generated lambda, which is unnamed.

David Rodríguez Ibeas

unread,
May 27, 2014, 8:45:15 AM5/27/14
to std-pr...@isocpp.org
I think this was addressed before:


On Sun, May 25, 2014 at 2:43 PM, Mikhail Semenov <mikhailse...@gmail.com> wrote:

We can have a similar definition for other cases, g([=] T x), etc.


The problem here is that when the user creates a lambda, at the place of creation, she can control how she wants the capture to be done. All the information is available to the one making the decision. In this case, the "capture" syntax is not at the place of call, but in the function declaration. The programmer defining the function cannot possibly know if the caller is going to capture anything at all or whether it makes sense to capture by value/reference.

Mikhail Semenov

unread,
May 27, 2014, 8:54:23 AM5/27/14
to std-pr...@isocpp.org, dib...@ieee.org


We can have a similar definition for other cases, g([=] T x), etc.


The problem here is that when the user creates a lambda, at the place of creation, she can control how she wants the capture to be done. All the information is available to the one making the decision. In this case, the "capture" syntax is not at the place of call, but in the function declaration. The programmer defining the function cannot possibly know if the caller is going to capture anything at all or whether it makes sense to capture by value/reference.
 
The user who writes the function knows what the function is doing:
(1) in most cases it would probably be [&];
(2) [=] special cases when the parameter in the called function is passed to other functions without being evaluated.
 

Mikhail Semenov

unread,
May 27, 2014, 10:05:15 AM5/27/14
to std-pr...@isocpp.org, dib...@ieee.org
On second thought: there might be the following approach:
 
f(lazy int x) // lazy, eval, expr  or whatever
{
     if (cond)
         send(x); // basically evaluated when first used or every time it is used.
}
 
It is very similar (or probably the same) as has been mentioned  during this discussion: the parameter is evaluated on first call (it does not really matter whether it is evaluated
once or many times - it may actually depend in implementation).
 
In terms of how many times it is evaluated we may discuss/vote do.
 
In some way this mechanism is equivalent to
 
f([&] int x) // lazy, eval or whatever
{
     if (cond)
         send(x());
}
 
In principle, I don't see much difference: either approach will.

 
 
 

Matthew Woehlke

unread,
May 27, 2014, 11:08:24 AM5/27/14
to std-pr...@isocpp.org
On 2014-05-27 08:45, David Rodríguez Ibeas wrote:
> The problem here is that when the user creates a lambda, at the place of
> creation, she can control how she wants the capture to be done. All the
> information is available to the one making the decision. In this case, the
> "capture" syntax is not at the place of call, but in the function
> declaration. The programmer defining the function cannot possibly know if
> the caller is going to capture anything at all or whether it makes sense to
> capture by value/reference.

The thunk cannot be propagated beyond the call (i.e. cannot be copied or
assigned, though it can be passed to a deeper call), thus, there is no
reason to capture by value. (More accurately, the thunk shall be
permitted to access the stack context of the caller.)

--
Matthew

Matthew Woehlke

unread,
May 27, 2014, 11:13:42 AM5/27/14
to std-pr...@isocpp.org
It's effectively a type modifier that applies to the whole type, similar
to e.g. volatile (except volatile, like const, would apply to part of
the type, e.g. 'int* volatile*' vs. 'int volatile**'). That being the
case, I don't understand what is a problem?

Can you give an example of something you're seeing as being a problem?

--
Matthew

Edward Catmur

unread,
May 27, 2014, 11:33:10 AM5/27/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net
void foo([[delayed]] int x);
using T = typename std::function<decltype(foo)>::argument_type;
// what is T?

Presumably T is [[delayed]] int; if so, where else is this type permitted to appear?

Mikhail Semenov

unread,
May 27, 2014, 11:56:25 AM5/27/14
to std-pr...@isocpp.org, mw_t...@users.sourceforge.net


void foo([[delayed]] int x);
using T = typename std::function<decltype(foo)>::argument_type;
// what is T?

Presumably T is [[delayed]] int; if so, where else is this type permitted to appear?
 
If you look from the point of view of code transformation, every appearance of x can be treated as calling a function of type std::function<int()>: x looks like a variable but in fact it is a function call. 
 
 
 
 

Matthew Woehlke

unread,
May 27, 2014, 12:12:54 PM5/27/14
to std-pr...@isocpp.org
On 2014-05-27 11:33, Edward Catmur wrote:
> On Tuesday, 27 May 2014 16:13:42 UTC+1, Matthew Woehlke wrote:
>> On 2014-05-26 15:02, Edward Catmur wrote:
>>> If you need the attribute to form a function pointer, then how
>>> can that be deduced by a template? I worry that semantic
>>> attributes will produce a category of second-class functions
>>> that can't be metaprogrammed correctly.
>>
>> Can you give an example of something you're seeing as being a problem?
>
> void foo([[delayed]] int x);
> using T = typename std::function<decltype(foo)>::argument_type;
> // what is T?
>
> Presumably T is [[delayed]] int;

('void([[delayed]] int)'?)

> if so, where else is this type permitted to appear?

Probably the same places as 'volatile', e.g. I would expect that
'template <typename T> void foo([[delayed]] T)' is legal, just like
'const T' would be legal in the same context. I would also expect it to
be stripped in the manner of 'const', e.g.:

void foo([[delayed]] int bar)
{
[[delayed]] auto _bar = bar; // _bar has type '[[delayed]] int'
auto __bar = _bar; // __bar has type 'int'
...
}

template <typename T> chase(T prey);
// 'chase([[delayed]] T prey)' would be legal

void cat_and_mouse(Cat cat, [[delayed]] Mouse mouse)
{
chase(mouse); // calls chase<Mouse>, not chase<[[delayed]] Mouse>
// also legal: chase<[[delayed]] Mouse>(mouse);
}

--
Matthew

walter1234

unread,
Jul 3, 2014, 4:51:45 AM7/3/14
to std-pr...@isocpp.org
Rust's situation is interesting here: They don't have pass by lambda, but what they do have is very compact syntax for lambda expressions.   `|args..|{ lambda block..}   |args|expression    ||expression`

their reasoning is you see costs explicitly at the call site.

e.g.  `do_something(regular_args,||something_to_lazy_eval)`

Could we get any closer to that in C++?   a shortcut for single expressions e.g. [](..)expr  is sugar for [](..){ return expr}`  and in turn `[]expr`  or `()expr` perhaps

David Krauss

unread,
Jul 3, 2014, 4:54:03 AM7/3/14
to std-pr...@isocpp.org
You’re proposing to eliminate only the curly braces? Is it worth it?

Note that the parens are already optional.

Ville Voutilainen

unread,
Jul 3, 2014, 5:06:08 AM7/3/14
to std-pr...@isocpp.org
On 3 July 2014 11:53, David Krauss <pot...@gmail.com> wrote:
How does one invoke the lambda immediately when the braces are omitted? With
the current syntax, I can do
for_each(vec.begin(), vec.end(), []{/*code here*/}()/* it's not the
lambda I'll use as the functor but its result*/);

Also, this more-terse syntax was already proposed in
http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3418.pdf
but was rejected, mostly due to readability concerns.

David Krauss

unread,
Jul 3, 2014, 5:12:08 AM7/3/14
to std-pr...@isocpp.org

On 2014–07–03, at 5:06 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:

> How does one invoke the lambda immediately when the braces are omitted? With
> the current syntax, I can do
> for_each(vec.begin(), vec.end(), []{/*code here*/}()/* it's not the
> lambda I'll use as the functor but its result*/);

Well, you would simply parenthesize the lambda :P

> Also, this more-terse syntax was already proposed in
> http://open-std.org/JTC1/SC22/WG21/docs/papers/2012/n3418.pdf
> but was rejected, mostly due to readability concerns.


As probably mentioned earlier in this thread, I plan on proposing decoration-free, lambda-like behavior with inline variables, but I’ve been procrastinating and now missed post-Rapperswil, in part because I anticipate such backlash. The proposal will need to be written with extreme care and political spin.

inkwizyt...@gmail.com

unread,
Jul 3, 2014, 12:28:00 PM7/3/14
to std-pr...@isocpp.org
What is point of calling one expression labda inplace? You should remove `[]` than trying add `()`.

Syntax `[](...)exp` is error prone, args and exp should be separated, `:` could do that:
int x;
auto f = [=]:(long)x; //long()
auto g = [=](long):x; //int(long)
auto h = [=]mutable:++x; //int()

Ville Voutilainen

unread,
Jul 3, 2014, 12:39:35 PM7/3/14
to std-pr...@isocpp.org
On 3 July 2014 19:28, <inkwizyt...@gmail.com> wrote:
> What is point of calling one expression labda inplace? You should remove
> `[]` than trying add `()`.

Did I say "one expression"?

>
> Syntax `[](...)exp` is error prone, args and exp should be separated, `:`
> could do that:
> int x;
> auto f = [=]:(long)x; //long()
> auto g = [=](long):x; //int(long)
> auto h = [=]mutable:++x; //int()

Yes, the current syntax separates the different parts nicely. Any tweaks should
probably try and maintain that separation.

How common are returning lambdas? People seem to claim they are so common
that they should have their own syntax. What is that based on?

inkwizyt...@gmail.com

unread,
Jul 3, 2014, 1:06:19 PM7/3/14
to std-pr...@isocpp.org


On Thursday, July 3, 2014 6:39:35 PM UTC+2, Ville Voutilainen wrote:
On 3 July 2014 19:28,  <inkwizyt...@gmail.com> wrote:
> What is point of calling one expression labda inplace? You should remove
> `[]` than trying add `()`.

Did I say "one expression"?

Then we have current lambda that can be easy call inplace.

>
> Syntax `[](...)exp` is error prone, args and exp should be separated, `:`
> could do that:
> int x;
> auto f = [=]:(long)x; //long()
> auto g = [=](long):x; //int(long)
> auto h = [=]mutable:++x; //int()

Yes, the current syntax separates the different parts nicely. Any tweaks should
probably try and maintain that separation.

How common are returning lambdas? People seem to claim they are so common
that they should have their own syntax. What is that based on?
 In C# I nearly ever use `delegat(arg){block}` styntax and stick only to `arg => expr`. Its sometimes required by LINQ expressions but even if it not require I still stick to it.
Another thing is this syntax is shorter by 10 characters, in extreme case its reduction of 70% key presses :)

Ville Voutilainen

unread,
Jul 3, 2014, 1:14:12 PM7/3/14
to std-pr...@isocpp.org
On 3 July 2014 20:06, <inkwizyt...@gmail.com> wrote:
>> > What is point of calling one expression labda inplace? You should remove
>> > `[]` than trying add `()`.
>> Did I say "one expression"?
> Then we have current lambda that can be easy call inplace.

Well, yes, we also have the current lambda which is short enough to write that I
don't need it to be shorter. I also don't have to teach users multiple
lambda syntaxes.

>> How common are returning lambdas? People seem to claim they are so common
>> that they should have their own syntax. What is that based on?
> In C# I nearly ever use `delegat(arg){block}` styntax and stick only to
> `arg => expr`. Its sometimes required by LINQ expressions but even if it not
> require I still stick to it.
> Another thing is this syntax is shorter by 10 characters, in extreme case
> its reduction of 70% key presses :)


If people want to reverse a decision EWG already made, they should
provide compelling
arguments. I'm not going to speculate what such compelling arguments
are, but I'd
guess it takes more than "C# lambdas are shorter" or "rust lambdas are shorter".
Reply all
Reply to author
Forward
0 new messages