[std-proposals] Why do closures not have a default constructor?

304 views
Skip to first unread message

Klaim - Joël Lamotte

unread,
May 24, 2016, 10:25:40 AM5/24/16
to std-pr...@isocpp.org
I am looking for the reason why lambda expressions are explicitly
not defining a default constructor (at least depending on the arguments constructors).

I cannot find a specific reason for that so far.
Does anyone here have a source about this point?


Otherwise I would like to propose to add 

    ClosureType() = default; 

to the definition of the closure generated by a lambda, if it's not already proposed somewhere.

This would help working with closures in generic context where we assume
that the callable type is regular (it's not enough but it helps);

For example with this kind of code:


template< class T, class Callable >
struct FatForwardIterator
{
  T value;
  Callable incr; // deactivate the default constructor if it's a closure
  //... etc

  FatForwardIterator& operator++()
  {
    // ...
    value = incr(value);
    return *this;
  }
};
// let's assume that the proposal to generate default comparisons is on...


template< class T, class Callable >
auto make_fat_iterator( T initial_value, Callable incr )
{
  return FatForwardIterator<T, Callable>{ initial_value, incr };
}

template< class IteratorType>
void some_algorithm( IteratorType begin, IteratorType end )
{
  IteratorType previous; // error: IteratorType do not have a default constructor.
  do
  {
    previous = begin;
    ++begin;
    // ... use previous
  }
  while( begin != end );
}


void foo()
{
  auto times2 = [](auto value){  return value * 2; };
  auto begin = make_fat_iterator( 1, times2 );
  auto end = make_fat_iterator( 8, times2 );

  // ... 
  some_algorithm( begin, end ); // fails to compile
}


Ville Voutilainen

unread,
May 24, 2016, 10:29:42 AM5/24/16
to ISO C++ Standard - Future Proposals
On 24 May 2016 at 17:25, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
> I am looking for the reason why lambda expressions are explicitly
> not defining a default constructor (at least depending on the arguments
> constructors).
>
> I cannot find a specific reason for that so far.
> Does anyone here have a source about this point?

If the lambda has captures, it's either insane or impossible to
default-construct it.
Treating non-capturing lambdas differently would require motivation that hasn't
appeared thus far, at least not in proposal form.

Klaim - Joël Lamotte

unread,
May 24, 2016, 10:49:40 AM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 16:29, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 24 May 2016 at 17:25, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
> I am looking for the reason why lambda expressions are explicitly
> not defining a default constructor (at least depending on the arguments
> constructors).
>
> I cannot find a specific reason for that so far.
> Does anyone here have a source about this point?

If the lambda has captures, it's either insane or impossible to
default-construct it.

Why? I do not see any justification or explanation of what is "insane" so far.
I do not see why a =default; could not be used 
to rely on capture's type default constructors, deleting the default constructor if it would not be possible for a normal type either.

If we had used a user-defined callable type instead of a lambda in the example I gave, it would have worked as expected.
Is there a reason for this lack of homogeneity?
 
 
Treating non-capturing lambdas differently would require motivation that hasn't
appeared thus far, at least not in proposal form.

I agree that both cases should have the exact same behaviour, but this is not really my point.
 

--
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAFk2RUYnh2_0dePfnbU%2Bk%3D3%2Bew1yid2t0%3Dv_Yb9VCjjhCUVzCQ%40mail.gmail.com.

Viacheslav Usov

unread,
May 24, 2016, 10:56:06 AM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 4:49 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

> Why? I do not see any justification or explanation of what is "insane" so far.

> I do not see why a =default; could not be used 

Do I understand correctly that you would expect that to fail when captures were not default-constructible?

And what about captures by references? What would they be initialized with?

Cheers,
V.

Ville Voutilainen

unread,
May 24, 2016, 11:02:54 AM5/24/16
to ISO C++ Standard - Future Proposals
On 24 May 2016 at 17:49, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>
>
> On 24 May 2016 at 16:29, Ville Voutilainen <ville.vo...@gmail.com>
> wrote:
>>
>> On 24 May 2016 at 17:25, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>> > I am looking for the reason why lambda expressions are explicitly
>> > not defining a default constructor (at least depending on the arguments
>> > constructors).
>> >
>> > I cannot find a specific reason for that so far.
>> > Does anyone here have a source about this point?
>>
>> If the lambda has captures, it's either insane or impossible to
>> default-construct it.
>
>
> Why? I do not see any justification or explanation of what is "insane" so
> far.

If the lambda has captures, those captures have values. A default
constructed lambda
will not have those same values. It's questionable whether it is of
the same type, and my
answer to that question is no.

> I do not see why a =default; could not be used
> to rely on capture's type default constructors, deleting the default
> constructor if it would not be possible for a normal type either.

Sure it could, but the problem isn't as much of whether the captures
are default-constructible,
but of what values they should have.

>> Treating non-capturing lambdas differently would require motivation that
>> hasn't
>> appeared thus far, at least not in proposal form.
> I agree that both cases should have the exact same behaviour, but this is
> not really my point.

That "agreement" is not quite right, because I'm not suggesting that
those cases should have
the exact same behavior.

Klaim - Joël Lamotte

unread,
May 24, 2016, 11:04:12 AM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 16:56, Viacheslav Usov <via....@gmail.com> wrote:
On Tue, May 24, 2016 at 4:49 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

> Why? I do not see any justification or explanation of what is "insane" so far.

> I do not see why a =default; could not be used 

Do I understand correctly that you would expect that to fail when captures were not default-constructible?


Yes.
 
And what about captures by references? What would they be initialized with?


The current behaviour (though I didn't check the exact wording) makes the closure copyable even when capturing
by reference.
It is unspecified (if my memory is correct) if data have to be stored when capturing by reference,
but when an implementation needs to store the implementation of the reference capture,
it imply the the actual stored data behave like (or is actually) a pointer.
Pointers can be default constructed.


Cheers,
V.

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

Viacheslav Usov

unread,
May 24, 2016, 11:15:24 AM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 5:04 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

And what about captures by references? What would they be initialized with?


The current behaviour (though I didn't check the exact wording) makes the closure copyable even when capturing
by reference.
It is unspecified (if my memory is correct) if data have to be stored when capturing by reference,
but when an implementation needs to store the implementation of the reference capture,
it imply the the actual stored data behave like (or is actually) a pointer.
Pointers can be default constructed.
 
Is that a way of saying that captures by reference would be bound to non-existing objects? Here is an excerpt from [dcl.ref] that makes this problematic:

A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. — end note ] 
[end]

Cheers,
V.

Klaim - Joël Lamotte

unread,
May 24, 2016, 11:16:23 AM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 17:02, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 24 May 2016 at 17:49, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>
>
> On 24 May 2016 at 16:29, Ville Voutilainen <ville.vo...@gmail.com>
> wrote:
>>
>> On 24 May 2016 at 17:25, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>> > I am looking for the reason why lambda expressions are explicitly
>> > not defining a default constructor (at least depending on the arguments
>> > constructors).
>> >
>> > I cannot find a specific reason for that so far.
>> > Does anyone here have a source about this point?
>>
>> If the lambda has captures, it's either insane or impossible to
>> default-construct it.
>
>
> Why? I do not see any justification or explanation of what is "insane" so
> far.

If the lambda has captures, those captures have values. A default
constructed lambda
will not have those same values. It's questionable whether it is of
the same type, and my
answer to that question is no.


Why would captured types be of different types if you are using the same closure type?
 
> I do not see why a =default; could not be used
> to rely on capture's type default constructors, deleting the default
> constructor if it would not be possible for a normal type either.

Sure it could, but the problem isn't as much of whether the captures
are default-constructible,
but of what values they should have.


Is'nt default construction's value when available a reasonnable value?
 
>> Treating non-capturing lambdas differently would require motivation that
>> hasn't
>> appeared thus far, at least not in proposal form.
> I agree that both cases should have the exact same behaviour, but this is
> not really my point.

That "agreement" is not quite right, because I'm not suggesting that
those cases should have
the exact same behavior.


If default construction of captured types is used, then the behaviour is uniform
and I do not consider non-capturing lambda expressions to be a special case.
This is what I meant. I'm not totally sure if there is a disagreement here.

Joël Lamotte



Ville Voutilainen

unread,
May 24, 2016, 11:18:42 AM5/24/16
to ISO C++ Standard - Future Proposals
On 24 May 2016 at 18:16, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>> If the lambda has captures, those captures have values. A default
>> constructed lambda
>> will not have those same values. It's questionable whether it is of
>> the same type, and my
>> answer to that question is no.
>>
>
> Why would captured types be of different types if you are using the same
> closure type?

Because if the captured types have different values, the closure types
are not same, by design.

>> > I do not see why a =default; could not be used
>> > to rely on capture's type default constructors, deleting the default
>> > constructor if it would not be possible for a normal type either.
>>
>> Sure it could, but the problem isn't as much of whether the captures
>> are default-constructible,
>> but of what values they should have.
>>
>
> Is'nt default construction's value when available a reasonnable value?

No. And it doesn't work for reference captures anyway.

Klaim - Joël Lamotte

unread,
May 24, 2016, 11:19:14 AM5/24/16
to std-pr...@isocpp.org
Interesting. So even if I propose a zero-intiialization for reference capture types, it would conflict with this.

 
Cheers,
V.

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

Klaim - Joël Lamotte

unread,
May 24, 2016, 11:21:13 AM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 17:19, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:


On 24 May 2016 at 17:15, Viacheslav Usov <via....@gmail.com> wrote:
On Tue, May 24, 2016 at 5:04 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

And what about captures by references? What would they be initialized with?


The current behaviour (though I didn't check the exact wording) makes the closure copyable even when capturing
by reference.
It is unspecified (if my memory is correct) if data have to be stored when capturing by reference,
but when an implementation needs to store the implementation of the reference capture,
it imply the the actual stored data behave like (or is actually) a pointer.
Pointers can be default constructed.
 
Is that a way of saying that captures by reference would be bound to non-existing objects? Here is an excerpt from [dcl.ref] that makes this problematic:

A reference shall be initialized to refer to a valid object or function. [ Note: in particular, a null reference cannot exist in a well-defined program, because the only way to create such a reference would be to bind it to the “object” obtained by indirection through a null pointer, which causes undefined behavior. As described in 9.6, a reference cannot be bound directly to a bit-field. — end note ] 
[end]


Interesting. So even if I propose a zero-intiialization for reference capture types, it would conflict with this.


So let's exclude captures by reference from this proposal idea then. 


Klaim - Joël Lamotte

unread,
May 24, 2016, 11:27:01 AM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 17:18, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 24 May 2016 at 18:16, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>> If the lambda has captures, those captures have values. A default
>> constructed lambda
>> will not have those same values. It's questionable whether it is of
>> the same type, and my
>> answer to that question is no.
>>
>
> Why would captured types be of different types if you are using the same
> closure type?

Because if the captured types have different values, the closure types
are not same, by design.


My understanding so far is not what you describe.
The closure type is always the same for one lambda expression,
but it's stored values can be whatever is possible values of the captured types.
For example:

#include <typeindex>
#include <cassert>

template<class T>
auto make_func(T value)
{
   return [=]{ return value; };
}


int main()
{
   assert(typeid(make_func(42)) == typeid(make_func(1234))); // succeed
}


That is, I make a clear difference beween the lambda expression and the generated 
anonymous closure type, which is still a type.
 
>> > I do not see why a =default; could not be used
>> > to rely on capture's type default constructors, deleting the default
>> > constructor if it would not be possible for a normal type either.
>>
>> Sure it could, but the problem isn't as much of whether the captures
>> are default-constructible,
>> but of what values they should have.
>>
>
> Is'nt default construction's value when available a reasonnable value?

No. And it doesn't work for reference captures anyway.
--
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.

jmo...@aldebaran.com

unread,
May 24, 2016, 11:31:35 AM5/24/16
to ISO C++ Standard - Future Proposals
Since user-defined types with reference members are not defaut constructible, by-reference capturing lambdas should not be default-constructible, I guess.
Concerning values of default-constructed members, for example :

// We're in a generic context and F is the type of a lambda.
F f; // should default construct members, so undefined value for integral types and pointers
F f{}; // should zero-initialize members (by constructing members with {})

Jeremy

Ville Voutilainen

unread,
May 24, 2016, 11:34:55 AM5/24/16
to ISO C++ Standard - Future Proposals
On 24 May 2016 at 18:26, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
> My understanding so far is not what you describe.
> The closure type is always the same for one lambda expression,
> but it's stored values can be whatever is possible values of the captured
> types.

[expr.prim.lambda]/4:

"The type of the lambda-expression (which is also the type of the
closure object) is a unique, unnamed
non-union class type — called the closure type — whose properties are
described below."

> For example:
>
> #include <typeindex>
> #include <cassert>
>
> template<class T>
> auto make_func(T value)
> {
> return [=]{ return value; };
> }
>
>
> int main()
> {
> assert(typeid(make_func(42)) == typeid(make_func(1234))); // succeed
> }

That looks like a bug to me. The lambdas returned from different
specializations of make_func are not
unique types.

> That is, I make a clear difference beween the lambda expression and the
> generated
> anonymous closure type, which is still a type.

The wording seems to disagree with you.

Klaim - Joël Lamotte

unread,
May 24, 2016, 11:46:24 AM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 17:34, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 24 May 2016 at 18:26, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
> My understanding so far is not what you describe.
> The closure type is always the same for one lambda expression,
> but it's stored values can be whatever is possible values of the captured
> types.

[expr.prim.lambda]/4:

"The type of the lambda-expression (which is also the type of the
closure object) is a unique, unnamed
non-union class type — called the closure type — whose properties are
described below."

> For example:
>
> #include <typeindex>
> #include <cassert>
>
> template<class T>
> auto make_func(T value)
> {
>    return [=]{ return value; };
> }
>
>
> int main()
> {
>    assert(typeid(make_func(42)) == typeid(make_func(1234))); // succeed
> }

That looks like a bug to me. The lambdas returned from different
specializations of make_func are not
unique types.


I do not see how types could be generated for each possible values (which depends
on the runtime). Or am I misunderstanding your point?
 
> That is, I make a clear difference beween the lambda expression and the
> generated
> anonymous closure type, which is still a type.

The wording seems to disagree with you.


I read that the lambda expression is a unique unnamed closure type.
I do not read that it's type is relative to the values of the captured objects.
However it does imply it's type it is relative to the _type_ of these captured objects
and that the initial value of the instance of the closure type does have an initial value relative
to the captured objects values.

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

Viacheslav Usov

unread,
May 24, 2016, 11:59:36 AM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 5:34 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
 
> template<class T>
> auto make_func(T value)
> {
>    return [=]{ return value; };
> }
>
>
> int main()
> {
>    assert(typeid(make_func(42)) == typeid(make_func(1234))); // succeed
> }

That looks like a bug to me. The lambdas returned from different specializations of make_func are not unique types.

I do not see different specializations here. make_func<int> is used in both cases.

I do not think I can follow the argument of either of you at this point, though. The original proposal has already been reduced to capture-by-value-only lambdas. Making that default constructible, contingent on default constructibility of the captured types, should be rather trivial. The question is, is there any really useful effect achievable through this and not achievable otherwise that is worth the effort? I am not convinced.

Cheers,
V.

Ville Voutilainen

unread,
May 24, 2016, 12:04:46 PM5/24/16
to ISO C++ Standard - Future Proposals
On 24 May 2016 at 18:46, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:
>> > For example:
>> >
>> > #include <typeindex>
>> > #include <cassert>
>> >
>> > template<class T>
>> > auto make_func(T value)
>> > {
>> > return [=]{ return value; };
>> > }
>> >
>> >
>> > int main()
>> > {
>> > assert(typeid(make_func(42)) == typeid(make_func(1234))); // succeed
>> > }
>>
>> That looks like a bug to me. The lambdas returned from different
>> specializations of make_func are not
>> unique types.
>>
>
> I do not see how types could be generated for each possible values (which
> depends
> on the runtime). Or am I misunderstanding your point?

Argh, sorry, I completely misread the example. I see what you mean now.

>> > That is, I make a clear difference beween the lambda expression and the
>> > generated
>> > anonymous closure type, which is still a type.
>>
>> The wording seems to disagree with you.
>>
>
> I read that the lambda expression is a unique unnamed closure type.
> I do not read that it's type is relative to the values of the captured
> objects.

Correct.

> However it does imply it's type it is relative to the _type_ of these
> captured objects
> and that the initial value of the instance of the closure type does have an
> initial value relative
> to the captured objects values.

Right. So, despite my momentary adventures to the crazy-land, we have
three issues here:

1) currently, it's not possible for a capturing lambda to have
captures that have default-constructed
values, they are the result of copy-initialization. Changing that is a
breaking change, because some
lunatic might rely on that. I don't have practical examples of that.

2) reference-capturing lambdas can't be default-constructed, nor can
lambdas which capture
non-default-constructible types. So not all lambdas can be
default-constructed. That doesn't seem
to be a huge issue. Not all lambdas can be copied, either, as that
won't work with captures of move-only
type.

3) for lambdas that don't depend on run-time values, users can
currently rely on the lambda only ever
having the values that were captured when the lambda was created.
Allowing default-construction with
some other values breaks that expectation. Again, I don't know whether
that's significant.

Nicola Gigante

unread,
May 24, 2016, 12:06:36 PM5/24/16
to std-pr...@isocpp.org
For what matters for this thread, the example can be rephrased
without templates, since it is calling the same instantiation of the
function anyway. If you change T, the type will be different, indeed.


Consider:

auto make_func(int value)
{
return [=]{ return value; };
}

Then call make_func(42) and make_func(1337).
How can you expect the lambda to change its type?
The captured value is a runtime effect of the execution of the program,
it cannot affect the type in this way. The return type of make_func
has to be always the same by definition. Functions have a well-defined type.

Klaim’s claim (eheh) is correct. Types of lambda functions are uniquely determined
independently from the captured *values*.

To return to the main issue, in my opinion it is perfectly sensible to provide
a default constructor to the closure type *when a user defined type with the
same members of the same type as captures would be able to have one*.

E.g.:

struct NC { NC(int x); };

{
int x;
NC nc{42};

auto f = [x]{ return x; };
auto g = [nc]{ return nc; };
}

In this case, decltype(f) should have a default constructor because ‘int’ can be default
constructed, while decltype(g) should not, because the NC struct is not default constructible.

It is perfectly coherent with how closure types are translated under the hood:

struct f_lambda_t
{
auto operator()() const {
return x;
}
private:
int x;
};

struct g_lambda_t
{
auto operator()() const {
return x;
}
private:
NC x;
};

If you add f_lambda_t() = default; and g_lambda_t() = default;, respectively, you
obtain what the original email was asking. For user defined types it would be perfectly
legal and well-defined. Also, someone brought up the issue about reference captures.
There is no issue. Put a reference in the struct above and the default constructor would
not be generated.

In a generic context, to me it is very annoying that closure types are not regular when
all the captured types are regular.

Bye,
Nicola



Nevin Liber

unread,
May 24, 2016, 12:13:49 PM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 11:06, Nicola Gigante <nicola....@gmail.com> wrote:
To return to the main issue, in my opinion it is perfectly sensible to provide
a default constructor to the closure type *when a user defined type with the
same members of the same type as captures would be able to have one*.

Why?  No one has yet provided motivation for this, and given that it is fairly painful to get the type of the lambda to declare another one (you need to pass it to a template or decltype), having a capturing lambda where the values are either the ones captured or ones that are default constructed doesn't seem all that useful.
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

Viacheslav Usov

unread,
May 24, 2016, 12:17:56 PM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 4:25 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

> IteratorType previous; // error: IteratorType do not have a default constructor.

This is your real problem. Not that lambdas are not default constructible. Your algorithm requires a default constructible iterator. This is not a general requirement for iterators. I am sure there is a way to have a default constructible iterator even if encapsulates something not default constructible.

I do not see how this can motivate your proposal.

Cheers,
V.

Klaim - Joël Lamotte

unread,
May 24, 2016, 12:29:46 PM5/24/16
to std-pr...@isocpp.org
Well, this example is directly inspired by Stepanov's book "Element's of Programming", page 106 (find_adjacent_mismatch_forward()),
where such a default initialization seems necessary.

Joël Lamotte


Klaim - Joël Lamotte

unread,
May 24, 2016, 12:31:51 PM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 18:13, Nevin Liber <ne...@eviloverlord.com> wrote:
On 24 May 2016 at 11:06, Nicola Gigante <nicola....@gmail.com> wrote:
To return to the main issue, in my opinion it is perfectly sensible to provide
a default constructor to the closure type *when a user defined type with the
same members of the same type as captures would be able to have one*.

Why?  No one has yet provided motivation for this, and given that it is fairly painful to get the type of the lambda to declare another one (you need to pass it to a template or decltype), having a capturing lambda where the values are either the ones captured or ones that are default constructed doesn't seem all that useful.

By "No one has yet provided motivation for this" do you mean that the initial example is not representative
of usual generic code development and usage?

 
--
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

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

Nevin Liber

unread,
May 24, 2016, 12:33:58 PM5/24/16
to std-pr...@isocpp.org
n 24 May 2016 at 11:31, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:


On 24 May 2016 at 18:13, Nevin Liber <ne...@eviloverlord.com> wrote:
On 24 May 2016 at 11:06, Nicola Gigante <nicola....@gmail.com> wrote:
To return to the main issue, in my opinion it is perfectly sensible to provide
a default constructor to the closure type *when a user defined type with the
same members of the same type as captures would be able to have one*.

Why?  No one has yet provided motivation for this, and given that it is fairly painful to get the type of the lambda to declare another one (you need to pass it to a template or decltype), having a capturing lambda where the values are either the ones captured or ones that are default constructed doesn't seem all that useful.

By "No one has yet provided motivation for this" do you mean that the initial example is not representative
of usual generic code development and usage?

I can't imagine generic code where I want an object in only one of two states, one of which I have very little control in setting up.

jmo...@aldebaran.com

unread,
May 24, 2016, 12:37:15 PM5/24/16
to ISO C++ Standard - Future Proposals
Note that in "Elements of programming" such a default constructed value is said to be in a "partially-formed state", i.e. it's only assignable or destructible.
So this means that it doesn't matter if the members are default-constructed with an undefined value. The main purpose is to have a virtually free default-construction.
 
Joël Lamotte


Viacheslav Usov

unread,
May 24, 2016, 12:39:35 PM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 6:29 PM, Klaim - Joël Lamotte <mjk...@gmail.com> wrote:

> Well, this example is directly inspired by Stepanov's book "Element's of Programming", page 106 (find_adjacent_mismatch_forward()), where such a default initialization seems necessary.

Again. I cannot see how that motivates a need for default constructible lambdas. Lambda is not a forward iterator, and it does not need to be one. If you want to wrap a lambda in an forward iterator, you can do so without default constructibility of lambdas.

Cheers,
V.

jmo...@aldebaran.com

unread,
May 24, 2016, 1:23:50 PM5/24/16
to ISO C++ Standard - Future Proposals
The initial example is, well, just an example.
The real motivation is to be able to manipulate lambdas as a Regular values.
It should be easy to use lambdas and compose them with the rest of the language.
For example, if I design an new type and want it to be comparable (==), its members should be comparable. If I want this new type to be default constructible (for example, if I want to store instances in an array, or be able to cheaply construct objects before having their values), its members should be default constructible.
If I'm doing generic programming and one of the members of this new type is an instance of a Callable type, there's no way for me to know if it's a lambda or user-defined type (and from a generic point of view, I don't care).
But the problem is, if this callable member is a user-defined type and it's default-constructible, the equivalent lambda won't.

Example:

// This is default constructible (if M is default-constructible).
template<typename M>
struct udf_incr {
  M m;
  M operator()(M const& n) const {return n * m;}
};

int m{2};
// This is not.
auto lambda_incr = [=](auto n) {return n * m;}

// concepts: Regular T, RegularFunction<T (T)> F
template<typename T, typename F>
struct advance_t {
  // advance_t is default constructible if T and F are default-constructible.
  T value;
  F incr_fun;
  void operator()() {
     value = incr_fun(value);
  }
};

Now, advance_t<int, udf_incr<int>> is default constructible, but advance_t<int, decltype(lambda_incr)> is not, even if they are semantically equivalent.
The same problem occurs with comparison if I add operator== to udf_incr.

The point is, in generic code it's not possible to use lambdas with types designed to be Regular, and it seems feasible to correct this.

Johannes Schaub

unread,
May 24, 2016, 1:32:47 PM5/24/16
to std-pr...@isocpp.org

Currently a lambda may rely on its captured by copy values to have not changed unless it itself changed them.

If you allow default construction, this invariant will break. Maybe bad.

>   IteratorType previous; // error: IteratorType do not have a default constructor.

>   do
>   {
>     previous = begin;
>     ++begin;
>     // ... use previous
>   }
>   while( begin != end );
> }
>
>
> void foo()
> {
>   auto times2 = [](auto value){  return value * 2; };
>   auto begin = make_fat_iterator( 1, times2 );
>   auto end = make_fat_iterator( 8, times2 );
>
>   // ... 
>   some_algorithm( begin, end ); // fails to compile
> }
>
>

> --
> You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.

> To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAOU91ONZtVMdUZQ_JpyVwcV0%2BsXVdUUcSnmgrzr4u6viuVk5WQ%40mail.gmail.com.

Johannes Schaub

unread,
May 24, 2016, 1:36:58 PM5/24/16
to std-pr...@isocpp.org

This also means that even though the corresponding member of the lambda is nonconst, a compiler can constant propagate copy captured members if the lambda body hasn't changed the closure copy data member.

Allowing default constructing members destroys this nice optimization opportunity.

Nicol Bolas

unread,
May 24, 2016, 1:55:03 PM5/24/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Tuesday, May 24, 2016 at 1:23:50 PM UTC-4, jmo...@aldebaran.com wrote:
The initial example is, well, just an example.
The real motivation is to be able to manipulate lambdas as a Regular values.

I think the question you're missing is "why do you want to?"

If you have a function which takes a function object to be called later, why would you want to manipulate such parameters "as a Regular values"?

Functions are not regular values. While some callable types may fit the Regular concept, they do not fit the actual semantics behind that concept.

Also, let's say I write this:

auto val = 30
auto lamb = [val]() {...}

That `...` part currently can be written under the assumption that the captured `val` will always be 30. However, under your rules, it won't. It may be 30, or it may be a default-constructed `int` (ie: undefined).

That's bad.

Sure, this example is rather simplistic. But `val` could be anything. It could be a raw pointer for the lambda to access. Those will not produce reasonable results under default construction.

The whole point of making lambdas a closure is that what they close over is well-defined. By allowing lambdas to be default constructed, the values they close over are no longer well-defined.

Matt Calabrese

unread,
May 24, 2016, 2:00:25 PM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 9:39 AM, Viacheslav Usov <via....@gmail.com> wrote:
Again. I cannot see how that motivates a need for default constructible lambdas.

It's never strictly a need, it's just a convenience.

It can also be argued that it is consistent with user-defined class types for any lambda where a corresponding type that had the capture types as datamembers would be a default constructible type (i.e. not for reference types, etc.), as was mentioned. I would personally say that it makes more sense to start with what's consistent with the rest of the language and only diverge from that if there is some kind of logical inconsistency. In other words, when adding a new feature such as lambdas (a little late now, I know), the burden, IMO, should be on *opponents* to argue why there should *not* be consistent rules with other types, as opposed to the proposal author needing to provide motivating cases for consistency. Language consistency should be a starting point because when features differ, those are more differences that users need to know about, making the language harder to learn and intuit (and in practice, it usually makes it harder to write sensible generic code).

FWIW, even though I lean towards more consistency with other class types, I have only personally found default-construction for captureless lambdas to be desirable in my particular use-cases, and that's all that I personally care for. Perhaps this is a more compelling example:

// Use a lambda as a stateless deleter function
std::unique_ptr<some_type, decltype([](some_type* ptr) { some_deletion_function(ptr); })> object(create_some_type());

Of course, you can't use a lambda in an unevaluated context currently, nor can one directly appear in a template argument, though there is/will be a pre-Oulu proposal to allow these to take place (leaving in the originally intended restriction that they do not appear in function declarations, IIRC). Even without that proposal there are similar uses, though they are a little less compelling since they aren't simple one-liners.

Viacheslav Usov

unread,
May 24, 2016, 2:02:14 PM5/24/16
to ISO C++ Standard - Future Proposals
On Tue, May 24, 2016 at 7:23 PM, <jmo...@aldebaran.com> wrote:

> The real motivation is to be able to manipulate lambdas as a Regular values.

This is a very general statement. Just having default constructibility won't do it. And unless you (or somebody else) make a consistent proposal for lambdas as fully regular values, the value of default constructibility per se is questionable. It may be that C++ will never have the other things that make lambdas fully regular.

Cheers,
V.

Nevin Liber

unread,
May 24, 2016, 2:11:20 PM5/24/16
to std-pr...@isocpp.org
On 24 May 2016 at 13:00, 'Matt Calabrese' via ISO C++ Standard - Future Proposals <std-pr...@isocpp.org> wrote:
On Tue, May 24, 2016 at 9:39 AM, Viacheslav Usov <via....@gmail.com> wrote:

FWIW, even though I lean towards more consistency with other class types, I have only personally found default-construction for captureless lambdas to be desirable in my particular use-cases, and that's all that I personally care for. Perhaps this is a more compelling example:

// Use a lambda as a stateless deleter function
std::unique_ptr<some_type, decltype([](some_type* ptr) { some_deletion_function(ptr); })> object(create_some_type());

Not particularly, as captureless lambdas are a different beast, because there is no difference between a copy constructed one and a default constructed one, as they have no state.

Matt Calabrese

unread,
May 24, 2016, 2:12:09 PM5/24/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Tue, May 24, 2016 at 10:55 AM, Nicol Bolas <jmck...@gmail.com> wrote:
On Tuesday, May 24, 2016 at 1:23:50 PM UTC-4, jmo...@aldebaran.com wrote:
The initial example is, well, just an example.
The real motivation is to be able to manipulate lambdas as a Regular values.

I think the question you're missing is "why do you want to?"

If you have a function which takes a function object to be called later, why would you want to manipulate such parameters "as a Regular values"?

Functions are not regular values. While some callable types may fit the Regular concept, they do not fit the actual semantics behind that concept.

Not to get side-tracked, but function objects can indeed be Regular and meet all of the semantic requirements of Regular types. This is often the case. Function pointers are Regular as are many user-defined function objects. Which are the semantic requirements that you claim function objects cannot meet? operator() is no different from any other kind of associated function of a concept and its existence as a requirement does not have any kind of effect on the ability for a model to be Regular. If this is unclear, just change the name of the associated function from "operator()" to "foo". Would you say that a concept with an associated function called "foo" cannot be Regular? Assuming you agree that such a type can be Regular, why would simply naming the function "operator()" instead of "foo" change anything?

That said, I think default construction as a requirement for Regular types was a mistake anyway because it's not really a requirement for any algorithms, it's just a convenience. Still it is often useful for convenience.

Matt Calabrese

unread,
May 24, 2016, 2:31:08 PM5/24/16
to ISO C++ Standard - Future Proposals
I'm not sure what the "not particularly" is referring to here. Are you saying that this is *not* a compelling example for default construction of a lambda? I agree that stateless lambdas are different in that they don't really raise any questions about whether or not the default-constructed value is useful. I personally only really care about allowing default-construction for captureless lambdas because those are the only cases that I personally encounter. Other similar motivating examples for default-constructible captureless lambdas are comparator functions for associative containers, etc..

I was only talking more generally with respect to default-construction of lambdas that have captures just on the principle that consistency should be considered important by default whenever proposing new language features, and that opponents should be the ones expected to have to argue *against* consistency if it's somehow a problem. I understand that starting with consistency is not an opinion that all people share, but it's useful to realize that that is where some people are coming from in discussions like this, which some people miss.

Nevin Liber

unread,
May 24, 2016, 2:56:38 PM5/24/16
to std-pr...@isocpp.org
// Use a lambda as a stateless deleter function
std::unique_ptr<some_type, decltype([](some_type* ptr) { some_deletion_function(ptr); })> object(create_some_type());

Not particularly, as captureless lambdas are a different beast, because there is no difference between a copy constructed one and a default constructed one, as they have no state.

I'm not sure what the "not particularly" is referring to here.

It doesn't address the capturing lambda case.
 
Are you saying that this is *not* a compelling example for default construction of a lambda?

It's a decent use case, although you have to change other parts of the language to make it work (you cannot have a lambda expression in an unevaluated operand).

Most uses I've seen for unique_ptr tend to be as members of a class, but putting a lambda in a header makes it way too easy for an accidental ODR violation.  Other use cases, such as calling factory functions, are solved by decltype and auto.
 
I was only talking more generally with respect to default-construction of lambdas that have captures just on the principle that consistency should be considered important by default whenever proposing new language features, and that opponents should be the ones expected to have to argue *against* consistency if it's somehow a problem. I understand that starting with consistency is not an opinion that all people share, but it's useful to realize that that is where some people are coming from in discussions like this, which some people miss.

Whenever consistency is brought up, the obvious question is "consistency with what?"  "Regular type" is not a concept in the standard.

Arthur O'Dwyer

unread,
May 24, 2016, 7:31:42 PM5/24/16
to ISO C++ Standard - Future Proposals
On Tuesday, May 24, 2016 at 11:00:25 AM UTC-7, Matt Calabrese wrote:
On Tue, May 24, 2016 at 9:39 AM, Viacheslav Usov <via....@gmail.com> wrote:
Again. I cannot see how that motivates a need for default constructible lambdas.

It's never strictly a need, it's just a convenience.
[...]
FWIW, even though I lean towards more consistency with other class types, I have only personally found default-construction for captureless lambdas to be desirable in my particular use-cases, and that's all that I personally care for. Perhaps this is a more compelling example:

// Use a lambda as a stateless deleter function
std::unique_ptr<some_type, decltype([](some_type* ptr) { some_deletion_function(ptr); })> object(create_some_type());

This is a use-case I have also encountered in the wild. I would very much like this (lambdas in unevaluated contexts, default-construction of captureless closure types) to Just Work.

Default-construction of captureful close types seems 100% dangerous and bad.

    int main() {
        int y = 42;
        auto x = [z=y](){ printf("%d\n", z); };
        decltype(x) x2;
        x2();  // does this print the "value" of a default-initialized int? that's super dangerous!
    }

We definitely shouldn't provide the user-programmer with any constructs that allow him to shoot himself in the foot this badly, especially when there's no motivating use-case for the feature.

I would say that there's one minorly thought-provoking corner case in between "captureless" and "captureful":

    int main() {
        auto x = [z=42](){ printf("%d\n", z); };
        decltype(x) x2;  // ought this to compile?
        x2();  // ought this to print 42?
    }

My opinion is that "no, it certainly oughtn't to print 42", based on my (correct?) belief that the meaning of [z=FOO] is not altered in any way by whether or not FOO is a constant-expression. In my ideal world, the above code would refuse to compile, because decltype(x) is quite definitely a captureful closure type.

Captureless closure types are already semantically more featureful than captureful closure types; namely, they provide an implicit conversion to function-pointer type. Providing a default constructor for captureless closure types would be logically in keeping with that existing behavior, IMO.

I see that existing behavior of captureless closure types as basically analogous to the way each new C++ standard adds new accessors to std::true_type:
- ::value
- default constructor + operator bool
- default constructor + operator()
It's convenient to be able to use std::true_type as if it "means" true, even though technically they're different things.

For captureless closure types, we're only at the start of that journey. So far we've added only
- operator <function-type>
but I think it makes sense to add
- default constructor
as well. This will enable user-programmers to more conveniently use captureless closure types as if they "mean" functions, even though technically they're different things.

–Arthur

Nicol Bolas

unread,
May 24, 2016, 8:02:13 PM5/24/16
to ISO C++ Standard - Future Proposals

That all sounds very reasonable. I particularly like not trying to create a third class of lambdas which capture constants rather than external variables.

Nevin Liber

unread,
May 24, 2016, 9:34:03 PM5/24/16
to std-pr...@isocpp.org

On 24 May 2016 at 18:31, Arthur O'Dwyer <arthur....@gmail.com> wrote:
This is a use-case I have also encountered in the wild. I would very much like this (lambdas in unevaluated contexts, default-construction of captureless closure types) to Just Work.

Then how do you prevent:

#ifndef HEADER_H_
#define HEADER_H_

struct A
{
    std::unique_ptr<B, decltype([](auto* p){delete_delete_delete(p);})> u;
    std::set<C, decltype([](auto const&l, auto const& r){return l > r;})> s;
}

#endif /* HEADER_H_ */

from being an accidental ODR violation when someone includes this in two translation units?

Miro Knejp

unread,
May 24, 2016, 10:21:38 PM5/24/16
to std-pr...@isocpp.org
Am 25.05.2016 um 03:33 schrieb Nevin Liber:

On 24 May 2016 at 18:31, Arthur O'Dwyer <arthur....@gmail.com> wrote:
This is a use-case I have also encountered in the wild. I would very much like this (lambdas in unevaluated contexts, default-construction of captureless closure types) to Just Work.

Then how do you prevent:

#ifndef HEADER_H_
#define HEADER_H_

struct A
{
    std::unique_ptr<B, decltype([](auto* p){delete_delete_delete(p);})> u;
    std::set<C, decltype([](auto const&l, auto const& r){return l > r;})> s;
}

#endif /* HEADER_H_ */

from being an accidental ODR violation when someone includes this in two translation units?
Clang uses file:line:column of the place of definition to distinguish closure types regardless of translation unit. I see no reason why your example should cause an ODR violation. It's just a matter of specifying in the standard whether it should be one or not so it's not implementation dependent.

Richard Smith

unread,
May 25, 2016, 3:09:18 AM5/25/16
to std-pr...@isocpp.org

On 24 May 2016 7:23 p.m., "Miro Knejp" <miro....@gmail.com> wrote:
>
> Am 25.05.2016 um 03:33 schrieb Nevin Liber:
>>
>>
>> On 24 May 2016 at 18:31, Arthur O'Dwyer <arthur....@gmail.com> wrote:
>>>
>>> This is a use-case I have also encountered in the wild. I would very much like this (lambdas in unevaluated contexts, default-construction of captureless closure types) to Just Work.
>>
>>
>> Then how do you prevent:
>>
>> #ifndef HEADER_H_
>> #define HEADER_H_
>>
>> struct A
>> {
>>     std::unique_ptr<B, decltype([](auto* p){delete_delete_delete(p);})> u;
>>     std::set<C, decltype([](auto const&l, auto const& r){return l > r;})> s;
>> }
>>
>> #endif /* HEADER_H_ */
>>
>> from being an accidental ODR violation when someone includes this in two translation units?
>
> Clang uses file:line:column of the place of definition to distinguish closure types regardless of translation unit.

Well, actually, clang uses the mangling system for the target (typically the vendor-neutral Itanium C++ ABI) to give the closure type the same name across translation units; typically this involves sequentially numbering the lambdas in each context where they can appear. (File:line:column would not be correct since that is not required to be the same in each such definition -- think about compiling preprocessed source for a practical case where this happens.)

> I see no reason why your example should cause an ODR violation. It's just a matter of specifying in the standard whether it should be one or not so it's not implementation dependent.

The standard is already clear that it would not be: under the terms of the odr, since you used the same token sequence to write the class definition (and met the side conditions for lookup results), the implementation is required to make your program act as if there is only one definition of the class, that is merely somehow visible from multiple translation units at once.

> --
> You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
> To post to this group, send email to std-pr...@isocpp.org.

> To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/ab7e1ab2-1558-23e6-8315-aff8d48dcc4e%40gmail.com.

jmo...@aldebaran.com

unread,
May 25, 2016, 5:10:29 AM5/25/16
to ISO C++ Standard - Future Proposals


Le mercredi 25 mai 2016 01:31:42 UTC+2, Arthur O'Dwyer a écrit :
On Tuesday, May 24, 2016 at 11:00:25 AM UTC-7, Matt Calabrese wrote:
On Tue, May 24, 2016 at 9:39 AM, Viacheslav Usov <via....@gmail.com> wrote:
Again. I cannot see how that motivates a need for default constructible lambdas.

It's never strictly a need, it's just a convenience.
[...]
FWIW, even though I lean towards more consistency with other class types, I have only personally found default-construction for captureless lambdas to be desirable in my particular use-cases, and that's all that I personally care for. Perhaps this is a more compelling example:

// Use a lambda as a stateless deleter function
std::unique_ptr<some_type, decltype([](some_type* ptr) { some_deletion_function(ptr); })> object(create_some_type());

This is a use-case I have also encountered in the wild. I would very much like this (lambdas in unevaluated contexts, default-construction of captureless closure types) to Just Work.

Default-construction of captureful close types seems 100% dangerous and bad.

    int main() {
        int y = 42;
        auto x = [z=y](){ printf("%d\n", z); };
        decltype(x) x2;
        x2();  // does this print the "value" of a default-initialized int? that's super dangerous!
    }

We definitely shouldn't provide the user-programmer with any constructs that allow him to shoot himself in the foot this badly, especially when there's no motivating use-case for the feature.

See above for motivating examples.
 

I would say that there's one minorly thought-provoking corner case in between "captureless" and "captureful":

    int main() {
        auto x = [z=42](){ printf("%d\n", z); };
        decltype(x) x2;  // ought this to compile?
        x2();  // ought this to print 42?
    }

My opinion is that "no, it certainly oughtn't to print 42", based on my (correct?) belief that the meaning of [z=FOO] is not altered in any way by whether or not FOO is a constant-expression. In my ideal world, the above code would refuse to compile, because decltype(x) is quite definitely a captureful closure type.

 
As explained above, the default constructor of the lambda should rely on the default constructor of its members (same as a user-defined type). Thus, the value of a default-constructed lambda would be meaningful depending of the meaningfulness of the values of its default-constructed members.
For example, imagine a lambda capturing a std::string, the default construction of the type of this lambda should default-constructs its std::string member, which has a meaningful value (the empty string).
Therefore, the value of this default-constructed lambda type is meaningful.

For a lambda capturing an int :
decltype(my_lambda_capturing_an_int) x; // the int member's value is undefined (members are default-constructed without empty braces)
decltype(my_lambda_capturing_an_int) x{}; // the int member's value is 0 (members are default-constructed with empty braces)

(though I don't remember if a type default-constructed with empty braces will recursively default-construct its members with empty braces).

This way, default-construction of a lambda type is consistent with user-defined types.

jmo...@aldebaran.com

unread,
May 25, 2016, 6:19:50 AM5/25/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
One precision regarding the default construction without a meaningful value, as in this case :
decltype(my_lambda_capturing_an_int) x;

The constructed value could be considered useless but again, following Stepanov (Elements of Programming, p7), you can decide to have the default construction of a type leaving the object in a partially formed state, meaning you can only assign a value to it or destroy it. The main gain is to have a free default construction by not initializing anything. See the initial example for a use-case, or for example find_adjacent_mismatch_forward (EoP, p106).

Nicol Bolas

unread,
May 25, 2016, 11:35:14 AM5/25/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com

I don't think you're getting the point.

If a lambda captures something, then the lambda function is either written with the expectation that the capture will have the exact captured value or it is not. If the function expects the value, then a default constructed value of that type is wrong and will cause the function to behave inappropriately.

There is no way to distinguish between these two cases. As such, whether it is valid to default construct a lambda that captures values is not known and cannot be known. Therefore, permitting the default construction of a capturing lambda creates fragile, brittle code.

That's bad.

This is like the rules of trivial copyability. It's possible to write classes which violate the rules of trivial copyability yet still technically could be. This is based on how the user-defined copy/move operations work and what data is being stored. But despite this, we don't allow trivially copying them to be well-defined C++ behavior. We only allow it to be well-defined in circumstances where we can be certain that such copying will be reasonable, where we can statically determine if it will always be legitimate.

The same goes here. The only case where we can be certain that default constructing a lambda will produce a valid lambda is if it is captureless.

jmo...@aldebaran.com

unread,
May 26, 2016, 6:14:55 AM5/26/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
I'm not sure to follow you.
If I write:

std::string suffix{"abcd"};
auto append = [=](std::string const& str) {return str + suffix;};

I expect the generated lambda type to be roughly equivalent to :

struct lambda_type {
    std::string suffix;
    lambda_type(std::string s) : suffix(std::move(s)) {}
    auto operator()(std::string const& str) const {return str + suffix;}
};
// ...
lambda_type append{"abcd"};

Why adding a default constructor to lambda_type would be wrong ?
The default-constructed string member would simply be empty.
As explained before, the meaningfulness of a default constructed lambda would be dependent on the meaningfulness of its default constructed members. It is the responsability of the programmer to determine if the default constructed lambda is meaningful or not. Note that it would be the exact same logic as for the default construction of any user-defined type and would thus be consistent with the rest of the language.

Also note that in the case of an arithmetic type:
int m{2};
auto add = [=](int n) {return m + n;};

I expect the generated lambda type to be roughly equivalent to :

struct lambda_type2 {
    int m;
    lambda_type2(int m) : m(m) {}
    auto operator()(int n) const {return m + n;}
};
// ...
lambda_type2 add{2};

In this case:
lambda_type2 add; // not meaningful because int is not initialized (see above for "partially formed state")
add = lambda_type2{2}; // Now ok (see the initial example for a motivation).

With also this case:
lambda_type2 add{}; // Ok: member initialized with empty braces, so value is 0.

As noted Matt Calabrese, it's all about consistency. The current behaviour of lambdas break the consistency with the equivalent user-defined types. When introducing a new feature in the language, it should be primordial to ensure that it works well with the other existing mechanisms of the language, i.e. to ensure it doesn't break consistency.

One of the fundamental mechanisms of the language is the ability to build new richer types based on existing types, by using existing types as members. This means that the capabilities of the new type depend on the capabilities of its members. For example, the new type is typically comparable only if all its members are comparable. Same thing for default construction. The consistency allows to reduce complexity (think of std::vector being able to store std::vector, without having to handle any special case because std::vector respects the same constrains that it put on its stored type).

So, if I design a new type in generic code and any Callable (sub)members happens to be a lambda, I have to drop default constructability. This seems an artificial limitation, as the equivalent user-defined type to this lambda type would be fine.
I'm talking here about default construction because it the topic of this proposal, but I think the case is even stronger for equality.

Regards,

Jeremy

Nicol Bolas

unread,
May 26, 2016, 10:42:15 AM5/26/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com

You explained why this would be wrong with your `lambda_type` example. Indeed, I have no idea why you posted it, since it undermines your entire argument.

First, you should recall that `lambda_type` as you have declared it is not default constructible. The rules for implicit default constructors is that they exist unless you add a constructor. Since `lambda_type` has a constructor, it won't get a default constructor unless you explicitly add one.

So your "consistency" argument doesn't work. The standards committee already ruled that such types are not implicitly default constructible. And they did so for a very good reason. Therefore, making types that would ordinarily be non-implicitly default constructible types into implicitly default constructible types would in fact be inconsistent with existing types.

Giving captureless lambdas a default constructor wouldn't be, since their constructor's argument list is already de-facto empty.

Furthermore, the semantic purpose of `lambda_type` in your example is to create an object which appends a string given at construction time to the back of a string parameter. While it is syntactically valid to default construct such an object, nobody would want to. Because if you did... it doesn't do anything. Calling the default-constructed lambda is an expensive no-op.

I would therefore assume most programmers would not declare a default constructor for such a type, because the whole point of the type is that it has a string. If the user wants to create one that appends the empty string, then the user can pass an empty string to the non-default constructor. That makes it abundantly clear what's going on.

And that's the best-case scenario for a default-constructed lambda: that it does something pointless but harmless. Consider this:

auto GetTextureBinder(GLenum target, GLuint tex_id)
{
 
if(!IsValidTarget(target)) throw std::runtime_error("Not a valid target.");
 
if(!glIsTexture(tex_id)) throw std::runtime_error("Not a valid texture");

 
return [target, tex_id]() {glBindTexture(target, tex_id);}
}

This function validates a parameter, then returns a function that uses that parameter. By the rules of C++ as they stand now, I have reasonable assurance that the returned functor will always have a valid target. There is nothing you can do to break this lambda externally without employing perfidy (or `glDeleteTexture(tex_id)`). And that in fact is the the goal of this function: to produce a lambda that binds the texture successfully, without having to match `target` with the texture it goes to..

Default constructing this type will produce a semantically meaningless object. To call that lambda will at bestproduce an OpenGL error; If it is default initialized, then you may get total garbage. And remember: the whole point of the lambda is to avoid such errors, to bind a valid texture and its appropriate target together.

So in your world, how do I get my reasonable assurance back? How do I turn off default construction of a lambda when that would lead to a non-functioning lambda? Should I put redundant checking in my lambda to make sure that values which were verified previously remain valid, even though the whole point was to ensure that it was valid before creating the lambda? Or do I have to abandon lambdas entirely and go back to a user-built `struct`?

The default-constructed string member would simply be empty.
As explained before, the meaningfulness of a default constructed lambda would be dependent on the meaningfulness of its default constructed members.

No, it depends on the meaningfulness of the lambda function when used with default constructed members.
 
It is the responsability of the programmer to determine if the default constructed lambda is meaningful or not.

Not under your proposal.

Responsibility has two parts: what you're have to do and the ability to do it. If it is impossible for you to do something, then you're not responsible for failing to do it.

Under your proposal, it is impossible for a programmer to turn off default construction of a lambda when it is not meaningful. If the types permit default construction, there is nothing the lambda writer can do about it.

And no, using special non-default-constructible wrappers in your captures is not a valid alternative.
 
As noted Matt Calabrese, it's all about consistency. The current behaviour of lambdas break the consistency with the equivalent user-defined types. When introducing a new feature in the language, it should be primordial to ensure that it works well with the other existing mechanisms of the language, i.e. to ensure it doesn't break consistency.

OK, the "consistency" ship sailed on lambdas when they decided that you had to stick `mutable` at the end in order to modify the captured values. Expecting consistency from lambdas is foolish at this point.

Also, as previously stated, "consistency" makes no sense here, because the types in a struct do not alone determine whether it is default constructible. So what are you trying to be "consistent" with here?

Lastly, consistency for its own sake is silly. Especially when that "consistency" creates the possibility of bugs that didn't exist before.

One of the fundamental mechanisms of the language is the ability to build new richer types based on existing types, by using existing types as members. This means that the capabilities of the new type depend on the capabilities of its members. For example, the new type is typically comparable only if all its members are comparable. Same thing for default construction.

I can turn off default construction. I can also turn on default construction even when types are not default constructible.

Your suggestion doesn't give me that power.
 
The consistency allows to reduce complexity (think of std::vector being able to store std::vector, without having to handle any special case because std::vector respects the same constrains that it put on its stored type).

Precisely what complexity is being reduced here?

Show me a template function which takes an arbitrary callable as a parameter, that would reasonably decide to call a default-constructed version of that callable.

Nobody would write such a thing. Why? Because you'd break with this:

int MyFunc();

CallTemplate(&MyFunc);

A default constructed function pointer is always a bad thing. Also:

std::function<int()> MyFunc = ...;

CallTemplate(MyFunc);

A default-constructed `function` is always a bad thing.

When it comes to non-functioning code, I'd personally rather get a compile error instead of a runtime crash.

The only reason I can see why you would ever want to default construct one is as a temporary measure, a stopgap on the way to performing a copy/move assignment into that value. And those cases are exceedingly rare, and can be dealt with by using `aligned_storage` and placement new.

So, if I design a new type in generic code and any Callable (sub)members happens to be a lambda, I have to drop default constructability. This seems an artificial limitation, as the equivalent user-defined type to this lambda type would be fine.

As previously explained, the "equivalent user-defined type" would not be default constructible. Also as previously explained, the ability to default construct a lambda does not ensure that calling the function will be valid. So if you were permitted to default construct them, you would not be able to tell whether it was valid to do so.

Therefore, your template code should not attempt to default construct them. So you've gained nothing.

The only lambdas that are guaranteed to be legitimate candidates for default construction are captureless ones.
 
I'm talking here about default construction because it the topic of this proposal, but I think the case is even stronger for equality.

Equality is irrelevant, since we'll get that with the implicit comparison operators proposal.

Nevin Liber

unread,
May 26, 2016, 10:57:18 AM5/26/16
to std-pr...@isocpp.org
On 26 May 2016 at 05:14, <jmo...@aldebaran.com> wrote:

std::string suffix{"abcd"};
auto append = [=](std::string const& str) {return str + suffix;};

I expect the generated lambda type to be roughly equivalent to :

struct lambda_type {
    std::string suffix;
    lambda_type(std::string s) : suffix(std::move(s)) {}
    auto operator()(std::string const& str) const {return str + suffix;}
};
// ...
lambda_type append{"abcd"};

Why wouldn't I just create a different lambda if I wanted to append to nothing as opposed to appending to a captured string?

Again, I'm not seeing why having a lambda with only two possible states is all that useful, especially in generic code.

Yes, I know you can abuse a mutable function call operator to add more states, but that is an awfully convoluted way to get around writing a class.
 
As noted Matt Calabrese, it's all about consistency. The current behaviour of lambdas break the consistency with the equivalent user-defined types.

Huh?  UDTs are not required to be default constructible.  UDTs typically can have far more than two states.
 
One of the fundamental mechanisms of the language is the ability to build new richer types based on existing types, by using existing types as members.

Which is not the intention of lambdas.  How do you envision using a capturing lambda (default constructed or not) as an aggregate member of another type?  Please post a code example of what you want to work in this case.
-- 

Thiago Macieira

unread,
May 26, 2016, 12:35:16 PM5/26/16
to std-pr...@isocpp.org
Em quinta-feira, 26 de maio de 2016, às 03:14:55 PDT, jmo...@aldebaran.com
escreveu:
> Also note that in the case of an arithmetic type:
> int m{2};
> auto add = [=](int n) {return m + n;};
>
> I expect the generated lambda type to be roughly equivalent to :
>
> struct lambda_type2 {
> int m;
> lambda_type2(int m) : m(m) {}
> auto operator()(int n) const {return m + n;}
> };
> // ...
> lambda_type2 add{2};

In this case and in many others, I'd expect the compiler to do constant
propagation and remove the member "m" completely. That would make the lambda:

struct lambda_type2 {
auto operator()(int n) const {return 2 + n;}
};

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

Arthur O'Dwyer

unread,
May 26, 2016, 2:35:40 PM5/26/16
to ISO C++ Standard - Future Proposals
On Thu, May 26, 2016 at 9:35 AM, Thiago Macieira <thi...@macieira.org> wrote:
Em quinta-feira, 26 de maio de 2016, às 03:14:55 PDT, jmo...@aldebaran.com
escreveu:
> Also note that in the case of an arithmetic type:
> int m{2};
> auto add = [=](int n) {return m + n;};
>
> I expect the generated lambda type to be roughly equivalent to :
>
> struct lambda_type2 {
>     int m;
>     lambda_type2(int m) : m(m) {}
>     auto operator()(int n) const {return m + n;}
> };
> // ...
> lambda_type2 add{2};

In this case and in many others, I'd expect the compiler to do constant
propagation and remove the member "m" completely. That would make the lambda:

struct lambda_type2 {
    auto operator()(int n) const {return 2 + n;}
};

Thiago, I think from a machine-level point of view you're certainly correct — but if you believe your comment to be relevant at the C++-language-semantics level, could you please clarify how?

Part of the point of my earlier post was to demonstrate that even these two lambdas

    auto captureless = [](int n) { return 2 + n; };
    auto captureful = [m=2](int n) { return m + n; };

have fundamentally different behaviors in C++. captureless is implicitly convertible to int(*)(int), whereas captureful is not implicitly convertible to int(*)(int), and by the current rules of the C++ language the compiler is not allowed to make it implicitly convertible to int(*)(int).

–Arthur

Arthur O'Dwyer

unread,
May 26, 2016, 3:02:36 PM5/26/16
to ISO C++ Standard - Future Proposals
On Wed, May 25, 2016 at 12:09 AM, Richard Smith <ric...@metafoo.co.uk> wrote:
> On 24 May 2016 7:23 p.m., "Miro Knejp" <miro....@gmail.com> wrote:
> > Am 25.05.2016 um 03:33 schrieb Nevin Liber:
> >> On 24 May 2016 at 18:31, Arthur O'Dwyer <arthur....@gmail.com> wrote:
> >>>
> >>> This is a use-case I have also encountered in the wild. I would very much like this
> >>> (lambdas in unevaluated contexts, default-construction of captureless closure types)
> >>> to Just Work.
> >>
> >> Then how do you prevent [...]

> >> accidental ODR violation when someone includes this in two translation units?
> >
> > [...] I see no reason why your example should cause an ODR violation. It's just a matter

> > of specifying in the standard whether it should be one or not so it's not implementation
> > dependent.
>
> The standard is already clear that it would not be: under the terms of the odr, since you
> used the same token sequence to write the class definition (and met the side conditions
> for lookup results), the implementation is required to make your program act as if there
> is only one definition of the class, that is merely somehow visible from multiple translation
> units at once.

Richard, could you expand on this a little bit, being as you're probably the one here who knows the most about the ODR? :)  It sounds to me as if you're using basically a technicality (the existence of a surrounding struct/class definition) to avoid the ODR in Nevin's specific case, but that you haven't addressed the possibility of ODR violations or plain old type mismatches in simpler cases.
For example, if we allowed decltype-of-lambda, would this program still be ill-formed?:

cat >alpha.h <<EOF
  EXTERN decltype([](){}) x;
EOF
cat >alpha.cc <<EOF
  #define EXTERN extern
  #include "alpha.h"  // declare x of type T
  #undef EXTERN
  #define EXTERN
  #include "alpha.h"  // define x of type U; if U!=T, the program is ill-formed, right?
  int main() {}
EOF
clang++ alpha.cc


How about this one, where the initializer of an inline variable (does the initializer count as part of the "definition" for purposes of the ODR?) consists of the same sequence of tokens everywhere, but also contains an implicit conversion from what I assume is a unique closure type, thus apparently falling foul of N4567 3.2/(6.4)?:

cat >beta.cc <<EOF
  inline int (*v)() = [](){ return 42; };
EOF
cat >gamma.cc <<EOF
  inline int (*v)() = [](){ return 42; };
  int main() {}
EOF
clang++ beta.cc gamma.cc


I mean, are you saying there are no lambda-related ODR problems, or are the problems just more subtle than the one Nevin suggested? (And if the only problems are subtle, isn't that kind of worse than if they were right out in the open?)

Could any of the problems (if any) be addressed by adding one more special rule to captureless lambdas: namely, that two captureless lambdas consisting of the same sequence of tokens are guaranteed to behave as if they correspond to the same definition of the lambda?  That is, what if tomorrow we made decltype([](){return 2;}) just as translation-unit-invariant as decltype(2) is today? I think we have the technology these days to make that happen (via the same mechanisms we use for inlines and templates), although it might need a lot of work in the mangler.

–Arthur

Nevin Liber

unread,
May 26, 2016, 6:43:51 PM5/26/16
to std-pr...@isocpp.org
On 26 May 2016 at 14:02, Arthur O'Dwyer <arthur....@gmail.com> wrote:
\Richard, could you expand on this a little bit, being as you're probably the one here who knows the most about the ODR? :)  It sounds to me as if you're using basically a technicality (the existence of a surrounding struct/class definition) to avoid the ODR in Nevin's specific case, but that you haven't addressed the possibility of ODR violations or plain old type mismatches in simpler cases.

To be clear, my only real concern about extending captureless lambdas is accidental ODR violations.  If we can get past that, it would be a useful feature.

I'm still firmly against extending capturing lambdas, as I just don't see a compelling reason to do so.

Nicol Bolas

unread,
May 26, 2016, 8:26:44 PM5/26/16
to ISO C++ Standard - Future Proposals
On Thursday, May 26, 2016 at 2:35:40 PM UTC-4, Arthur O'Dwyer wrote:
On Thu, May 26, 2016 at 9:35 AM, Thiago Macieira <thi...@macieira.org> wrote:
Em quinta-feira, 26 de maio de 2016, às 03:14:55 PDT, jmo...@aldebaran.com
escreveu:
> Also note that in the case of an arithmetic type:
> int m{2};
> auto add = [=](int n) {return m + n;};
>
> I expect the generated lambda type to be roughly equivalent to :
>
> struct lambda_type2 {
>     int m;
>     lambda_type2(int m) : m(m) {}
>     auto operator()(int n) const {return m + n;}
> };
> // ...
> lambda_type2 add{2};

In this case and in many others, I'd expect the compiler to do constant
propagation and remove the member "m" completely. That would make the lambda:

struct lambda_type2 {
    auto operator()(int n) const {return 2 + n;}
};

Thiago, I think from a machine-level point of view you're certainly correct — but if you believe your comment to be relevant at the C++-language-semantics level, could you please clarify how?

He's not trying to say that, at the language level, one should be equivalent to the other. He's saying that the compiler has the choice right now to optimize the capturing lambda "as if" it were a non-capturing one. You don't get the language distinction, but you do get the performance.

With the proposed change, forcing capturing lambdas to be default constructible, that optimization would now become impossible.

Arthur O'Dwyer

unread,
May 26, 2016, 8:29:25 PM5/26/16
to ISO C++ Standard - Future Proposals
Ah, I see. That makes sense.

–Arthur

Richard Smith

unread,
May 27, 2016, 1:31:26 AM5/27/16
to std-pr...@isocpp.org
On Thu, May 26, 2016 at 12:02 PM, Arthur O'Dwyer <arthur....@gmail.com> wrote:
On Wed, May 25, 2016 at 12:09 AM, Richard Smith <ric...@metafoo.co.uk> wrote:
> On 24 May 2016 7:23 p.m., "Miro Knejp" <miro....@gmail.com> wrote:
> > Am 25.05.2016 um 03:33 schrieb Nevin Liber:
> >> On 24 May 2016 at 18:31, Arthur O'Dwyer <arthur....@gmail.com> wrote:
> >>>
> >>> This is a use-case I have also encountered in the wild. I would very much like this
> >>> (lambdas in unevaluated contexts, default-construction of captureless closure types)
> >>> to Just Work.
> >>
> >> Then how do you prevent [...]
> >> accidental ODR violation when someone includes this in two translation units?
> >
> > [...] I see no reason why your example should cause an ODR violation. It's just a matter
> > of specifying in the standard whether it should be one or not so it's not implementation
> > dependent.
>
> The standard is already clear that it would not be: under the terms of the odr, since you
> used the same token sequence to write the class definition (and met the side conditions
> for lookup results), the implementation is required to make your program act as if there
> is only one definition of the class, that is merely somehow visible from multiple translation
> units at once.

Richard, could you expand on this a little bit, being as you're probably the one here who knows the most about the ODR? :)  It sounds to me as if you're using basically a technicality (the existence of a surrounding struct/class definition) to avoid the ODR in Nevin's specific case, but that you haven't addressed the possibility of ODR violations or plain old type mismatches in simpler cases.

It may be a technicality, but it's an important one that's reasonably central to the rules that make class / inline function definitions in header files work in C++. Here's another case of the same rule:

// foo.h
struct A {
  struct { int x, y; } s;
};

decltype(A().s) is the same across translation units because of this rule. And another:

// bar.h
inline auto f() {
  return [] {};
}

f() can be defined in multiple translation units only because the lambda has the same type in each instance.

For example, if we allowed decltype-of-lambda, would this program still be ill-formed?:

cat >alpha.h <<EOF
  EXTERN decltype([](){}) x;
EOF
cat >alpha.cc <<EOF
  #define EXTERN extern
  #include "alpha.h"  // declare x of type T
  #undef EXTERN
  #define EXTERN
  #include "alpha.h"  // define x of type U; if U!=T, the program is ill-formed, right?
  int main() {}
EOF
clang++ alpha.cc

Yes, each occurrence of the [](){} token sequence here would create a distinct closure type; that would be ill-formed. 

How about this one, where the initializer of an inline variable (does the initializer count as part of the "definition" for purposes of the ODR?) consists of the same sequence of tokens everywhere, but also contains an implicit conversion from what I assume is a unique closure type, thus apparently falling foul of N4567 3.2/(6.4)?:

cat >beta.cc <<EOF
  inline int (*v)() = [](){ return 42; };
EOF
cat >gamma.cc <<EOF
  inline int (*v)() = [](){ return 42; };
  int main() {}
EOF
clang++ beta.cc gamma.cc

That would be fine, just as it would if the lambda were instead defined inside an inline function or class definition. The program behaves as if there is exactly one definition of the variable.

I mean, are you saying there are no lambda-related ODR problems, or are the problems just more subtle than the one Nevin suggested? (And if the only problems are subtle, isn't that kind of worse than if they were right out in the open?)

The latter. Do you have a plausible example where the ODR would be violated but no diagnostic would be produced? It seems likely to me that problems would often look like your EXTERN case above and would be reliably diagnosed, but maybe there are other kinds of likely failure mode.

Could any of the problems (if any) be addressed by adding one more special rule to captureless lambdas: namely, that two captureless lambdas consisting of the same sequence of tokens are guaranteed to behave as if they correspond to the same definition of the lambda?  That is, what if tomorrow we made decltype([](){return 2;}) just as translation-unit-invariant as decltype(2) is today? I think we have the technology these days to make that happen (via the same mechanisms we use for inlines and templates), although it might need a lot of work in the mangler.

That sounds pretty painful. But yes, that'd solve the problem :) 

jmo...@aldebaran.com

unread,
May 27, 2016, 4:58:26 AM5/27/16
to ISO C++ Standard - Future Proposals


Le jeudi 26 mai 2016 16:57:18 UTC+2, Nevin ":-)" Liber a écrit :
On 26 May 2016 at 05:14, <jmo...@aldebaran.com> wrote:

std::string suffix{"abcd"};
auto append = [=](std::string const& str) {return str + suffix;};

I expect the generated lambda type to be roughly equivalent to :

struct lambda_type {
    std::string suffix;
    lambda_type(std::string s) : suffix(std::move(s)) {}
    auto operator()(std::string const& str) const {return str + suffix;}
};
// ...
lambda_type append{"abcd"};

Why wouldn't I just create a different lambda if I wanted to append to nothing as opposed to appending to a captured string?

Again, I'm not seeing why having a lambda with only two possible states is all that useful, especially in generic code.

See the example below.
 

Yes, I know you can abuse a mutable function call operator to add more states, but that is an awfully convoluted way to get around writing a class.
 
As noted Matt Calabrese, it's all about consistency. The current behaviour of lambdas break the consistency with the equivalent user-defined types.

Huh?  UDTs are not required to be default constructible.  UDTs typically can have far more than two states. 
 
One of the fundamental mechanisms of the language is the ability to build new richer types based on existing types, by using existing types as members.

Which is not the intention of lambdas.  How do you envision using a capturing lambda (default constructed or not) as an aggregate member of another type?  Please post a code example of what you want to work in this case.

Here is an more complex example that should compile. I hope this will answer your questions.
It's useful to default construct I in find_last.

#include <iterator>
#include <utility>
#include <type_traits>
#include <forward_list>
#include <string>
#include <iostream>

// Useful in this example.
template<typename T>
bool operator!=(T const& a, T const& b) {
    return !(a == b);
}

// Find the iterator right before end, that is the last iterator pointing to
// a valid element of the half-open bounded range.
template<typename I> // ForwardIterator I
I find_last(I b, I e) {
    // Precondition: bounded_range(b, e) && b != e
    I i; // It's possible to do this only if all I's members are default constructible, which is not the case if there is a lambda.
    // Note that it doesn't matter here if the default-constructed value is meaningful or not.
    do {
        i = b;
        ++b;
    } while (b != e);
    return i;
}

// In this case, it is useful to default contruct i, because we don't have the value yet, and the scope of i exceed the loop.

// A Transformation is a Regular Callable with the signature T (T).
// We could also use an Action (semantically equivalent to a Transformation,
// but with the signature void (T&)) if the assignment of I is expensive.
template<typename I, typename F> // ForwardIterator I, Transformation<I> F
struct iter_transfo_t {
    I i;
    F f; // This is a Callable, and it would be nice if it could be a lambda.
    // ...
    iter_transfo_t& operator++() {
        i = f(i);
        return *this;
    }
    friend bool operator==(iter_transfo_t const& a, iter_transfo_t const& b) {
        // For now, doesn't compile if f is a lambda, but let's assume it's ok.
        return a.i == b.i && a.f == b.f;
    }
    // As a side note, would be also nice to have operator<(), for example to be able to use this type
    // as the key type of a map.
    // The definition would be (a.i < b.i) || (!(b.i < a.i) && a.f < b.f),
    // assuming that < on I follows the trichotomy law (meaning
    // !(i < j) && !(j < i) implies i == j).
    // ...
};

template<typename I> // ForwardIterator I
auto strided_iter(I&& i, size_t step) {
    auto f = [=](I i) {std::advance(i, step); return i;};
    return iter_transfo_t<std::decay_t<I>, decltype(f)>{std::forward<I>(i), f};
}

// Let's imagine the elements follow the pattern
// firstname0, lastname0, firstname1, lastname1, ...
// We could use this to find the last firstname.
template<typename I> // ForwardIterator I
I find_last_even_positionned(I b, I e) {
    // Precondition: bounded_range(b, e) && std::distance(b, e) >= 2 && std::distance(b, e) % 2 == 0
    return find_last(strided_iter(b, 2u), strided_iter(e, 2u)).i;
}

// We could implement other algorithms similar to find_last_name, by using other transformations,
// (typically implemented by lambdas) for example to find the last prime number of a range, and so on.
// The transformations allow to implement any traversal of the range.

int main() {
    using namespace std;
    // The only purpose of using a forward_list is to show that the algorithm works with forward iterators.
    forward_list<string> names{"John", "Rambo", "Marty", "McFly", "Indiana", "Jones"};
    auto it_first_name = find_last_even_positionned(begin(names), end(names));
    cout << *it_first_name << '\n'; // output Indiana
    return 0;
}
 
-- 
 Nevin ":-)" Liber  <mailto:ne...@eviloverlord.com>  +1-847-691-1404

If we replace the lambda in strided_iter by the following type, everything is ok :

struct advance_t {
    size_t step;
    template<typename I>
    I operator()(I i) const {
        std::advance(i, step);
        return i;
    }
    friend bool operator==(advance_t const &a, advance_t const& b) {
        return a.step == b.step;
    }
    // Not mandatory in the above example, but nice to have:
    friend bool operator<(advance_t const &a, advance_t const& b) {
        return a.step < b.step;
    }
};

template<typename I> // ForwardIterator I
auto strided_iter(I&& i, size_t step) {
    return iter_transfo_t<std::decay_t<I>, advance_t>{std::forward<I>(i), {step}};
}


Regards,

Jeremy

FrankHB1989

unread,
May 27, 2016, 7:35:49 AM5/27/16
to ISO C++ Standard - Future Proposals


在 2016年5月24日星期二 UTC+8下午10:25:40,Klaim - Joël Lamotte写道:
I am looking for the reason why lambda expressions are explicitly
not defining a default constructor (at least depending on the arguments constructors).

I cannot find a specific reason for that so far.
Does anyone here have a source about this point?


Otherwise I would like to propose to add 

    ClosureType() = default; 

to the definition of the closure generated by a lambda, if it's not already proposed somewhere.

This would help working with closures in generic context where we assume
that the callable type is regular (it's not enough but it helps);

For example with this kind of code:


template< class T, class Callable >
struct FatForwardIterator
{
  T value;
  Callable incr; // deactivate the default constructor if it's a closure
  //... etc

  FatForwardIterator& operator++()
  {
    // ...
    value = incr(value);
    return *this;
  }
};
// let's assume that the proposal to generate default comparisons is on...


template< class T, class Callable >
auto make_fat_iterator( T initial_value, Callable incr )
{
  return FatForwardIterator<T, Callable>{ initial_value, incr };
}

template< class IteratorType>
void some_algorithm( IteratorType begin, IteratorType end )
{
  IteratorType previous; // error: IteratorType do not have a default constructor.
  do
  {
    previous = begin;
    ++begin;
    // ... use previous
  }
  while( begin != end );
}


void foo()
{
  auto times2 = [](auto value){  return value * 2; };
  auto begin = make_fat_iterator( 1, times2 );
  auto end = make_fat_iterator( 8, times2 );

  // ... 
  some_algorithm( begin, end ); // fails to compile
}


Why should an iterator be DefaultConstructible?
 

Daniel Krügler

unread,
May 27, 2016, 7:41:19 AM5/27/16
to std-pr...@isocpp.org
2016-05-27 13:35 GMT+02:00 FrankHB1989 <frank...@gmail.com>:
>
>
> 在 2016年5月24日星期二 UTC+8下午10:25:40,Klaim - Joël Lamotte写道:
>>
>> I am looking for the reason why lambda expressions are explicitly
>> not defining a default constructor (at least depending on the arguments
>> constructors).
>>
>> I cannot find a specific reason for that so far.
>> Does anyone here have a source about this point?
>>
>>
>> Otherwise I would like to propose to add
>>
>> ClosureType() = default;
>>
>> to the definition of the closure generated by a lambda, if it's not
>> already proposed somewhere.
>>
>> This would help working with closures in generic context where we assume
>> that the callable type is regular (it's not enough but it helps);
>>
>> For example with this kind of code:
>>
>>
>> template< class T, class Callable >
>> struct FatForwardIterator
>> {
>> T value;
>> Callable incr; // deactivate the default constructor if it's a closure
>> //... etc
>>
>> FatForwardIterator& operator++()
>> {
>> // ...
>> value = incr(value);
>> return *this;
>> }
>> };
>> // let's assume that the proposal to generate default comparisons is on...

[..]

>>
> Why should an iterator be DefaultConstructible?

There is no DefaultConstructible requirement for an InputIterator or
an OutputIterator, but there is one from ForwardIterator on (indicated
by the naming of the OPs types there seems to be a forward iterator
involved).

- Daniel

FrankHB1989

unread,
May 27, 2016, 8:10:15 AM5/27/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com


在 2016年5月26日星期四 UTC+8下午6:14:55,jmo...@aldebaran.com写道:
It sounds absurd to require arbitrary data types having capability of holding not-constructed/indeterminate state. For integer types that may be reasonable because the allowed representations are required to be uniformly mapped to the underlying storage in some predicable manners.
Note that allowing indeterminate value stored in object of such types is already not consistent, e.g. reading uninitialized unsigned char vs. int.

One of the fundamental mechanisms of the language is the ability to build new richer types based on existing types, by using existing types as members. This means that the capabilities of the new type depend on the capabilities of its members. For example, the new type is typically comparable only if all its members are comparable. Same thing for default construction. The consistency allows to reduce complexity (think of std::vector being able to store std::vector, without having to handle any special case because std::vector respects the same constrains that it put on its stored type).

Strictly speaking, these existing types are ill-designed. Unless I'm working on some particular forms of interop, I want optional<T> but not T*; similarly, I better have some indeterminate<T> rather than T with indeterminate state specified in subtle additional rules of the language. I do not like to pay for what I deliberately don't want to have, because it will distorts my intent and causes unnecessary mental cost.
 
So, if I design a new type in generic code and any Callable (sub)members happens to be a lambda, I have to drop default constructability. This seems an artificial limitation, as the equivalent user-defined type to this lambda type would be fine.
I'm talking here about default construction because it the topic of this proposal, but I think the case is even stronger for equality.

Are you serious? Equality is not strictly necessary in every theories, but any formal systems (e.g. set theories) being practical and general enough will provide some sorts of it, otherwise you can't determine the identity of things being described. (And for theories do not provide one, they will rely on the base theories which provide it.) You may not use it explicitly because the underlying system has implied it, for example, the C++ type system (which is nominal) provides type equivalence (btw, sadly std::is_same cannot be overloaded) being widely used in many contexts of the language. To implement such a system using meta language (here it is C++), an equality function is always needed, so it is better provided in a standard form, to make the use consistent. On the other hand, the property of "being constructible without parameters" or "having exact one standardized undetermined state" is definitely not so general in both meta languages and object languages. It is occasionally useful in C++ as an object language because the default constructor is specified being one of "special member functions", despite there is actually no need to make it special like a copy constructor or a destructor because the default-initialization is concerned with less contexts in the language (mostly required due to being compatible with C).

FrankHB1989

unread,
May 27, 2016, 8:44:24 AM5/27/16
to ISO C++ Standard - Future Proposals


在 2016年5月27日星期五 UTC+8下午7:41:19,Daniel Krügler写道:
Yes, I should mean that specifically for forward iterators. However, for iterators mostly used (e.g. as member type of containers), they have to meet the requirement of being DefaultConstructible. Note forward iterators are required to be value-initialized to be a general pass-the-end indicator of an empty sequence which I find no algorithms relies on; I think it superfluous.
- Daniel

Nicol Bolas

unread,
May 27, 2016, 11:29:37 AM5/27/16
to ISO C++ Standard - Future Proposals, jmo...@aldebaran.com
On Friday, May 27, 2016 at 4:58:26 AM UTC-4, jmo...@aldebaran.com wrote:
Le jeudi 26 mai 2016 16:57:18 UTC+2, Nevin ":-)" Liber a écrit :
On 26 May 2016 at 05:14, <jmo...@aldebaran.com> wrote:

std::string suffix{"abcd"};
auto append = [=](std::string const& str) {return str + suffix;};

I expect the generated lambda type to be roughly equivalent to :

struct lambda_type {
    std::string suffix;
    lambda_type(std::string s) : suffix(std::move(s)) {}
    auto operator()(std::string const& str) const {return str + suffix;}
};
// ...
lambda_type append{"abcd"};

Why wouldn't I just create a different lambda if I wanted to append to nothing as opposed to appending to a captured string?

Again, I'm not seeing why having a lambda with only two possible states is all that useful, especially in generic code.

See the example below.
 

Yes, I know you can abuse a mutable function call operator to add more states, but that is an awfully convoluted way to get around writing a class.
 
As noted Matt Calabrese, it's all about consistency. The current behaviour of lambdas break the consistency with the equivalent user-defined types.

Huh?  UDTs are not required to be default constructible.  UDTs typically can have far more than two states. 
 
One of the fundamental mechanisms of the language is the ability to build new richer types based on existing types, by using existing types as members.

Which is not the intention of lambdas.  How do you envision using a capturing lambda (default constructed or not) as an aggregate member of another type?  Please post a code example of what you want to work in this case.

Here is an more complex example that should compile. I hope this will answer your questions.
It's useful to default construct I in find_last.

So you have a type `iter_transfo_t` which needs to be default constructible. But that type needs to store something which is decidedly not default constructible. The only saving grace of this concept is that, while default constructing ForwardIterators is legal, it's not legal to actually do anything with them without copying into them. So just work around the limitation. If `F` is default constructible, then use it. If `F` is not, then don't store `F` directly. Use a `std::function` or a lighter-weight wrapper which is default constructible. Here's one example of such a wrapper:

template<typename T>
class DefaultConstructible
{
public:
   
DefaultConstructible() = default;
   
   
DefaultConstructible(const DefaultConstructible &other)
       
: t(new(&t_data) T(other) {}
   
DefaultConstructible(DefaultConstructible &&other)
       
: t(new(&t_data) T(std::move(other)) {}
   
const DefaultConstructible &operator=(const DefaultConstructible &other)
   
{
       
if(t)
       
{
           
if(other.t)
               
*t = *other.t;
           
else
           
{
                t
->~T();
                t
= nullptr;
           
}
       
}
       
else
       
{
           
if(other.t)
                t
= new(&t_data) T(*other.t);
       
}
       
return *this;
   
}
   
const DefaultConstructible &operator=(DefaultConstructible &&other)
   
{
       
if(t)
       
{
           
if(other.t)
               
*t = std::move(*other.t);
           
else
           
{
                t
->~T();
                t
= nullptr;
           
}
       
}
       
else
       
{
           
if(other.t)
                t
= new(&t_data) T(std::move(*other.t));
       
}
       
return *this;
   
}
   
   
operator T*() {return t;}
   
operator const T*() const {return t;}
   
    T
& operator*() {return *t;}
   
const T& operator*() {return *t;}
   
   
//SFINAE protect if DefaultCosntructible is not equality comparable.
   
bool operator==(const DefaultConstructible &rhs) const
   
{
       
if(t && rhs.t)
           
return *t == *rhs.t;
       
       
return false; //If one of them doesn't have a proper value yet, you can't compare them reasonably.
   
}
   
   
//Add other comparisons as needed.

   
~DefaultConstructible() {if(t) t->~T();}
   
private:
    std
::aligned_storage<sizeof(T), alignof(T)>::type t_data;
    T
*t = nullptr;
};

Reply all
Reply to author
Forward
0 new messages