--
---
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/.
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.
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).
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);}
Actually, type erasure does not require dynamic allocation if the function type does not have to be copyable.
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?
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.
auto f = <some lambda>;
std::function g(std::ref(f));
auto f = <some lambda>;
std::function g(&f);
--
---
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/.
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.
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.
void foo(function<void ()> x);
foo(std::ref([&] { /* ... */ }));
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([&] { /* ... */ }));
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;
}
--
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.
void logMessage(int level, const std::string &message) {
if (level < globalLogLevel) {
std::cout << message;
}
}
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).
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).
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.
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.
I propose to allow function parameter declarations to be prefixed with [&].
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 forthe 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 makethem 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 veryclearly.Bye,Nicola
void g(std::string [&] arg) {
arg = "the new value";
std::cout << arg; // This is not "the new value"!!!
}
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.
void foo([&] int x); //Defined in another translation unit
void bar ([&] int x){
foo(x);
}
--
void foo([[lazy]] std::function<string ()> arg) {
cout << arg();
}
template<typename T>
void bar([[lazy]] T const& arg) {
cout << arg();
}
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.
auto foo(string [&] arg) {
return [=]() { return arg; };
}
auto foo(string [&] arg) {
auto unused = arg;
return [=]() { return arg; };
}
foo(1/0)
void foo(int [&] arg) {
int a = arg; // Divide by zero???
}
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.
Others may have expressed that desire. I still prefer that the calleereserves 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.
void f([&] T x)
{
if (b)
a = x();
}
f(e);
void f1(function<T()> expr)
{
if (b)
a = expr();
}
f1([&]()->T { return e;});
void p([&] void x)
{
if (b)
x(); // we are only interested in side effects, no need of the value.
}
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.
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.
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 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.
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();
}
We can have a similar definition for other cases, g([=] T x), etc.
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.
void foo([[delayed]] int x);
using T = typename std::function<decltype(foo)>::argument_type;
// what is T?
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?
int x;
auto f = [=]:(long)x; //long()
auto g = [=](long):x; //int(long)
auto h = [=]mutable:++x; //int()
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?