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.
--
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.
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 usedDo 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.
--
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/CAA7YVg0qVNfpi12bi_xKBw64q96xOoGw%3DR-SaiOKp8VT9ewFbQ%40mail.gmail.com.
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 capturingby 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.
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.
--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.
To view this discussion on the web visit https://groups.google.com/a/isocpp.org/d/msgid/std-proposals/CAA7YVg3oH23ZGhhfcooqtj4jD%2Bfk0fG320db0YuO3eVi%3DOGC0Q%40mail.gmail.com.
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 capturingby 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.
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.
--
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/CAFk2RUa0NBwSSLf2sAdgdvyrkZ7LKDu60DgWYrcpK1h_b%3DHLJA%40mail.gmail.com.
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.
--
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/CAFk2RUa%3D8emeBYAP-3BHtvt%3DQiw3RDjwPEn4zsoHrsrv%2BreB1g%40mail.gmail.com.
> 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.
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*.
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
--
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/CAGg_6%2BMNO%3DifeLz%2BTmNdkyJ33GH10Sn_pGcrfcy0j7V%2B94B4hQ%40mail.gmail.com.
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 representativeof usual generic code development and usage?
Joël Lamotte
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.
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.
The initial example is, well, just an example.The real motivation is to be able to manipulate lambdas as a Regular values.
auto val = 30
auto lamb = [val]() {...}
Again. I cannot see how that motivates a need for default constructible lambdas.
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 functionstd::unique_ptr<some_type, decltype([](some_type* ptr) { some_deletion_function(ptr); })> object(create_some_type());
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.
// Use a lambda as a stateless deleter functionstd::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.
Are you saying that this is *not* a compelling example for default construction of a lambda?
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.
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 functionstd::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.
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?
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.
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 functionstd::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.
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);}
}
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.
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).
int MyFunc();
CallTemplate(&MyFunc);
std::function<int()> MyFunc = ...;
CallTemplate(MyFunc);
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.
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"};
As noted Matt Calabrese, it's all about consistency. The current behaviour of lambdas break the consistency with the equivalent user-defined types.
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.
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;}
};
\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.
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?
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.
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.
I am looking for the reason why lambda expressions are explicitlynot 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 addClosureType() = 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 assumethat 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//... etcFatForwardIterator& 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}
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.
- Daniel
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.
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;
};