Sign in to follow this  
Krohm

Assigning lambdas

Recommended Posts

Since the new C++ standard became accessible and implemented, I've been looking at ways to play a bit with it for integration. Although I've never really been on this [i]functional programming as silver bullet[/i] bandwagon, I agree the ability to define functions on the fly is pretty much better than having to deal with the function pointers. Not to mention capture lists, which I'm considering promoting to the next best thing after sliced bread.

However, I'm afraid I don't understand something - the way functions are supposed to be passed around in VC2010.

I want to thank Washu for [url="http://www.gamedev.net/blog/32/entry-2254352-the-new-c-functions/"]his article on functional programming[/url]. From there, I get the idea that functors work just like everything else. They can be passed by reference such as in [font=courier new,courier,monospace]MulticastEvent::AddHandler[/font] or by value, by storing in [font=courier new,courier,monospace]std::vector<EventSignature>[/font].
Great, but I'm having some difficulty in understanding a behavior here.
Consider:
[CODE]
#include <iostream>
#include <functional>
using namespace std;
typedef function<void()> PrintFunc;

void Nothing(const PrintFunc &blah) {
blah();
}
void Nothing_value(const PrintFunc blah) {
blah();
}

struct Callbacks {
PrintFunc const& func; // (2)
Callbacks(PrintFunc const& call) : func(call) { }
};


int main() {
auto lines = []() { cout<<"- - - - - -"<<endl; };
auto dots = []() { cout<<". . . . . ."<<endl; };
Nothing(lines);
Nothing_value(dots);
{
Callbacks one(lines); // (1)
one.func();
}
{
Callbacks two(dots);
two.func();
}
return 0;
}
[/CODE]
Everything seems to be fine up to line (1). Here, I suppose I have a complex function taking lots of callbacks I want to encapsulate in a single struct, similarly to how C# dispatches events via delegates as far as I've understood.
The result of this code is that the reference contained in the struct won't be initialized correctly. It is not initialized even immediately after creation. The situation shows in the debugger as attached.

However, if I change line (2) to value type, everything works as expected.

Is this a quirk in the compiler or am I missing something?

Share this post


Link to post
Share on other sites
[s]Works fine in GCC. Perhaps a compiler bug?[/s]

[edit: rather, it [i]appears[/i] to work fine, but is actually incorrect. Read below for further explanation] Edited by rip-off

Share this post


Link to post
Share on other sites
VS10 is not entirely standards compliant in regards to lambdas (for instance, you can't assign a lambda directly to a C-style function pointer).

That being said, there is no way your code should work. [i]lines[/i] is [b]not[/b] an instance of std::function, but an instance of Visual C++'s built-in lambda type. You are then passing a [i]temporary object[/i] of type std::function<void()>, and then assigning it to a reference, in effect, taking the address of a temporary value.

The proper fix would be to do as you did; change line (2) to be a value, and not a reference.

The fact that GCC works is actually surprising.

Share this post


Link to post
Share on other sites
Visual Studio, even the latest 2011 beta is [url="http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx"]next to useless[/url] for C++0x.

2010 is not even in contest.

To mess with C++0x, use GCC, at least for next several years. CLang also appears to be a good choice.

A [url="http://wiki.apache.org/stdcxx/C%2B%2B0xCompilerSupport"]different comparision[/url] (note the partial support, even for VS12.0, almost everything is way behind GCC). Edited by Antheus

Share this post


Link to post
Share on other sites
[quote name='Antheus' timestamp='1335894802' post='4936472']
Visual Studio, even the latest 2011 beta is [url="http://blogs.msdn.com/b/vcblog/archive/2011/09/12/10209291.aspx"]next to useless[/url] for C++0x.

2010 is not even in contest.

To mess with C++0x, use GCC, at least for next several years.
[/quote]

This still isn't a bug in VS10 or 11; they are both behaving properly. He's taking the address of a temporary value; since he's running a debug build, I assume that VC is zeroing out the temporary after it is out of scope, hence why it is 'empty' (null). I don't know why it works in GCC (and I reckon that it shouldn't) - it's probably not zeroing out the temporary; still undefined behavior, it just happens to work.

He needs to stop storing a reference and needs to store a value.

Share this post


Link to post
Share on other sites
[quote name='Ameise' timestamp='1335894948' post='4936474']

He needs to stop storing a reference and needs to store a value.
[/quote]

At first I thought you're refering to 'Nothing'.

I missed the Callbacks. Yay for inconsistent naming conventions...

[code] PrintFunc const& func; // (2) [/code]

Above is very risky. It's correct and allowed, but very error prone. I also remember VS raising "cannot generate assignment operator" when doing that.

Share this post


Link to post
Share on other sites
Wow, great.
Please all, explain me in a way I can actually understand. I have never been given the opportunity of reading the official C/C++ ISO spec nor to spend my effort at looking for language design in details.

[quote name='Ameise' timestamp='1335893902' post='4936466']
The fact that GCC works is actually surprising.
...
This still isn't a bug in VS10 or 11; they are both behaving properly.
[/quote][i]Formally [/i]problem solved: so much for designing a language that now has two types of lambdas.
What is a correct syntax?

[quote name='Antheus' timestamp='1335894802' post='4936472']
Visual Studio, even the latest 2011 beta is next to useless for C++0x.
...
Above is very risky. It's correct and allowed, but very error prone. I also remember VS raising "cannot generate assignment operator" when doing that.
[/quote]I suppose I will change C++11 [i]next to useless[/i] for me. But anyway, what's wrong and risky? I took the syntax from Washu's article. I now see that article works on very subtle details (besides there's no [font=courier new,courier,monospace]AddDelegate[/font]) functions and structs are supposed to be blindly cast there.
And to be completely honest, this is what I'd suppose it had to work for something properly useful.
Even the way types are supposed to be extracted from lambdas are fairly odd to me.

I mean, everybody talking about those things and then what I found out is that even GCC - which actually does the [i]expected [/i]thing - is [i]formally [/i]wrong?
This whole thing starts to smell like nonsense to me.

Share this post


Link to post
Share on other sites
[quote name='Krohm' timestamp='1335943527' post='4936684']
Wow, great.
Please all, explain me in a way I can actually understand. I have never been given the opportunity of reading the official C/C++ ISO spec nor to spend my effort at looking for language design in details.
[/quote]
This line:
[code] Callbacks one(lines); // (1)[/code]
Constructs a Callbacks passing in a lambda. Since lambda is not a valid type for the constructor of Callbacks, a temporary object of type std::function<void()> is constructed instead and passed. Since you indicate the parameter as a CONSTANT REFERENCE, you can pass an unnamed temporary value (such as the std::function<void()> just constructed).

The instant that line is done evaluated the TEMPORARY VALUE that was constructed is destructed. Your Callbacks class now holds a reference to an unnamed temporary that no longer exists.

In fact, you can make it explicit (but with the same bug):
[code] Callbacks one(PrintFunc(lines)); // (1)[/code]

by changing this line:
[code] PrintFunc const& func; // (2)[/code]
to
[code] PrintFunc func; // (2)[/code]
you cause your constructor to invoke the function copy constructor, taking the unnamed temporary you passed and copying it. Thus avoiding holding on to a reference to a soon to be dead object.

Thus the corrected code:
[code]

#include <iostream>
#include <functional>

using namespace std;
typedef function<void()> PrintFunc;

void Nothing(const PrintFunc &blah) {
blah();
}
void Nothing_value(const PrintFunc blah) {
blah();
}

struct Callbacks {
PrintFunc func; // (2)
Callbacks(PrintFunc const& call) : func(call) { }
};


int main() {
auto lines = []() { cout<<"- - - - - -"<<endl; };
auto dots = []() { cout<<". . . . . ."<<endl; };
Nothing(lines);
Nothing_value(dots);
{
Callbacks one(lines); // (1)
one.func();
}
{
Callbacks two(dots);
two.func();
}
return 0;
}[/code] Edited by Washu

Share this post


Link to post
Share on other sites
[quote]. But anyway, what's wrong and risky? I took the syntax from Washu's article.[/quote]

Take a simpler example:[code]struct Foo {
const std::string & s;

Foo(const std::string * str) : s(str) {};
}

Foo f("Hello World"); // string is const char *[/code]

When Hello World gets passed into function, it's implicitly converted into std::string using the std::string::string(const char *) constructor, thereby creating a temporary. This temporary is then stored in s. It ceases to exist once the constructor ends.

Same happens above. lambda is not an function<>, but it gets converted into one with same flaws as above.

Implicit conversion occurs because constructor accepts *const* reference. It it merely accepted 'PrintFunc &' without const, implicit conversion shouldn't be possible and should raise an error or warning.


Storing objects by reference this way is "risky" precisely because of implicit conversions. Also because one cannot properly implement assignment operator, meaning objects storing references are non-copyable.


As mentioned, it's not strictly wrong as far as C++ is concerned, it's just up to you to make sure references remain valid in same way compiler won't complain about invalid pointers.

Share this post


Link to post
Share on other sites
I guess I have to apologize. I clearly have an excess of right-brain, intuitive, big-picture thinking. When I asked what's wrong with that, I didn't have the need to get explanations on why the temporary goes out of scope.
Seriously, I have been thinking at this the whole night and there are two cases[list=1]
[*]The language/compiler supports lambdas properly
[*]Or it does not - like it's the case here
[/list]
Now, it's clear at this point what was going on, so the rest is mostly rant. Or maybe not.

[quote name='rip-off' timestamp='1335866224' post='4936366']
[b]Works fine in GCC.[/b] Perhaps a compiler bug?
[/quote]I mean seriously... we are bashing even the [i]expected [/i]thing just because we want to be [i]formally [/i]correct?
Because in my opinion what GCC is doing is [i]The Right Thing[/i] and I don't think they have made it work because of a misconception, an error or a bug. I think it's the right thing to do if we claim to have support for "functional programming" in some language - define a function as you want - you don't change what it is.

[b]My problem here is that I never figured out that the compiler would have cast something in the first place and yes, it was clear to me there were different types going to clash.[/b] But heck, we are talking about function pointers!
[CODE]
struct Callbacks {
void (*func)(void); // (2)
Callbacks(void (*func)(void)) : func(call) { }
};
[/CODE]We are not talking about some strange object thing around... that's function pointers at their core but ok, say they are some kind of objects, I would expect, from a properly implemented compiler - [size=3]it was clear since start VC2010's C++11 was not properly implemented but I didn't expect it so bad[/size] - to mandate a relationship like what I draw in the attachment.
Yes, those arrows are really meant to be read as "IS-A" relationships.
Note that in this case no casts would be involved and no temporaries would have been generated.
Because if an environment claims to support lambdas... I guess special mangling is happening behind the scenes.

Am I completely unreasonable in thinking no cast should be involved? Maybe a bit but completely... I'm quite sure I'm not. [b]Works fine in GCC[/b]. Why? Because I guess GCC authors looked beyond the spec to match the rationale involved: call it some way and define it in code, define it through [i]lambda construct[/i] or by an [font=courier new,courier,monospace]operator()[/font], or by using [font=courier new,courier,monospace]std::function[/font], they did care about doing the right thing - passing this pointer around. If we would be in 1980 I could understand something is going to break but in post-2000? It was not clear to me.

And it's not a completely crazy thing - I think - , some [font=courier new,courier,monospace]for [/font]loops might emit the same code as some [font=courier new,courier,monospace]while [/font]loops. Intrinsics look like functions but do all sorts of weird data manipulation. Want to talk about OpenMP pragmas?
It's not completely crazy from a purely almost-theorical point of view as well - is this functional or not? Then we care about signature only. It's not that doing [font=courier new,courier,monospace]typedef char sbyte[/font] changes what happens at the basic level and that's the same behavior I would expect from a compiler - functions don't change what they are at their core... define them as you want. Because I don't see how lambdas should change what a function is - and I'm not even considering capture lists.

So I guess at this point [b]my hate goes to Microsoft[/b], because even taking in consideration what you have written - which I consider valuable and reasonable but sort of missing the point - I still think they could have considered making an explicit ctor call from lambdas, or emit a warning or whatever, since they clearly had to ship a rushed implementation (not even casting correctly to function pointer), I guess they could have gone at least so far, I guess the sentiment is shared.

[quote name='Antheus' timestamp='1335965831' post='4936751']
Storing objects by reference this way is "risky" precisely because of implicit conversions. Also because one cannot properly implement assignment operator, meaning objects storing references are non-copyable.[/quote]As a side note I still don't quite understand why references cannot be copied. It's not like an automatically dereferenced pointer is some kind of funky entity. Why they couldn't be assigned I could understand but copy... but that's not the point anyway, I am aware of this problem.

I want to thank you all for your patience. Edited by Krohm

Share this post


Link to post
Share on other sites
...

You appear to still not be understanding the issue.

Furthermore, it works in GCC... RIGHT UP UNTIL YOU DO SOMETHING WITH THE STACK. At which point it breaks horribly. FOR THE EXACT SAME REASON.

Share this post


Link to post
Share on other sites
[quote]
[quote name='rip-off' timestamp='1335866224' post='4936366']
[b]Works fine in GCC.[/b] Perhaps a compiler bug?
[/quote]
I mean seriously... we are bashing even the [i]expected [/i]thing just because we want to be [i]formally [/i]correct?
Because in my opinion what GCC is doing is [i]The Right Thing[/i] and I don't think they have made it work because of a misconception, an error or a bug. I think it's the right thing to do if we claim to have support for "functional programming" in some language - define a function as you want - you don't change what it is.
[/quote]
Note: I just ran the code, I didn't notice storing the value by const reference. As such, my comment was incorrect. I should have said, at most, "[u]Appears[/u] to work fine in GCC".

Share this post


Link to post
Share on other sites
[quote]My problem here is that I never figured out that the compiler would have cast something in the first place and yes, it was clear to me there were different types going to clash.[/quote]

It's not a cast, it's implicit conversion.

Going back to string example:[code]void foo(const string & str);

const char * s;
foo(s);[/code]Obviously, s cannot be passed to str, they are unrelated types.

But string has a constructor which takes const char *.

So compiler creates new string by passing s into its constructor. This is the temporary object. Same thing happens with lambdas - a new function<> instance is created as temporary.

Which is why it's recommended to declare constructors with 'explicit' to prevent these accidental conversions. String doesn't use explicit to work transparently with C-style strings. Otherwise, one would need to manually perform the conversion:[code]foo(std::string(s)); // which is potentially ambiguous as function call
// so
std::string temp(s);
foo(temp);
// vs
foo("Hello World");[/code]



[quote]As a side note I still don't quite understand why references cannot be copied.[/quote]

They can be, but don't produce desirable result. Simple example, using pointers to make it clearer:[code]struct A {
int * x;
}

A a;
a.x = new int(10);

A b;
b.x = a.x; // wrong
delete a.x; // boom
---
b.x = new int( *a.x); // right, deep copy
delete a.x; // no problem

[/code]It is impossible to properly implement assignment operator or rule of three with references alone - and there is no mechanism to provide a deep copy, since references don't provide life cycle information. Even if they did, it wouldn't help, since they might refer to read-only storage, stack, part of another structure, heap or just about any other valid memory location, many of which cannot be copied, but can be destroyed. Edited by Antheus

Share this post


Link to post
Share on other sites
No. I don't understand. And clearly, I cannot even explain. To quote myself:
[quote name='Krohm' timestamp='1335943823']
This relies on a subtle detail: the fact that push_back performs a value copy.[/quote]And that was clear hours ago. Now I get an explanation for the delete semantics of shared pointers... ok, I get the point that I am the only one thinking a function should be the same "thing" whatever it was declared/defined - because that would imply no casts/conversions would be necessary.
The choice of the word "cast" was not taken lightly: in my understanding of the system a pointer cast (supposing doing derived-to-base pointer conversions qualify as a cast) would have been sufficient but ok, there's no problem. My understanding of the feature was incorrect that's now clear because I had the impression the system was completely "fluid". The thing that was not clear to me as I written already was that different types were involved - as I noted, I would have expected them to alias. Because of "language support" but I clearly overstated the capability.

How are lambda types supposed to be expressed in declarations? I would be tempted to do something like
[CODE]
typedef [](){}->void PrintFunc;
[/CODE]
But it does not appear to work. I think I've seen somewhere something involved instantiating a function and capturing its type. Is [font=courier new,courier,monospace]std::function[/font] still the way to do this? Do you have any hints to protect myself from eventual typos - I am used to work mostly by referencing and I guess I could benefit sooner or later from a guard.

Share this post


Link to post
Share on other sites
The type of a lambda function is a [i]unique anonymous[/i] functor type for every lambda function. Two separate lambdas that have the exact same definition in the exact same scope will have two separate types. As such, trying to get at the exact type of a lambda function is an exercise in futility. Just store the std::function by value.

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1336052848' post='4937096']
The type of a lambda function is a [i]unique anonymous[/i] functor type for every lambda function. Two separate lambdas that have the exact same definition in the exact same scope will have two separate types. As such, trying to get at the exact type of a lambda function is an exercise in futility. Just store the std::function by value.
[/quote]
With that in mind, it made me curious if
[code]auto lines = []() { cout<<"- - - - - -"<<endl; };
auto dots = []() { cout<<". . . . . ."<<endl; };
lines = dots;
[/code]
works. I don't have a compiler on my current machine to test if the assignment gives a compile time error. [s]if no one answers this before I get to a proper computer and can test this myself, I'll just edit this with what I found.[/s]

[edit]

Got that sooner than I thought I would. For my little test, the type of [font=courier new,courier,monospace]lines[/font] is [font=courier new,courier,monospace]const main()::<lambda()>&[/font] and the assignment generates a compile time error.

However, a normal function pointer worked:
[code]#include <iostream>
#include <functional>

typedef void (*function) ();

int main()
{
function lines = []() { std::cout << " - - - - - " << std::endl; };
auto dots = []() { std::cout << " . . . . . " << std::endl; };

lines = dots;

lines();
dots();
}
[/code]

But yes, I would just go with a [font=courier new,courier,monospace]std::function[/font]. Edited by Cornstalks

Share this post


Link to post
Share on other sites
[quote name='Cornstalks' timestamp='1336053544' post='4937102']
However, a normal function pointer worked:
[/quote]
A conversion to function pointer exists for the function object types of lambdas that don't capture any variables. It should be pretty obvious why lambdas that do capture variables don't have that conversion.

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1336065913' post='4937149']
[quote name='Cornstalks' timestamp='1336053544' post='4937102']
However, a normal function pointer worked:
[/quote]
A conversion to function pointer exists for the function object types of lambdas that don't capture any variables. It should be pretty obvious why lambdas that do capture variables don't have that conversion.
[/quote]

[CODE]
#include <cstdio>
#include <functional>

int main ()
{
volatile int a = 5;
std::function<void ()> func = [a] () { printf("%i\n", a); };
func();
return 0;
}
[/CODE]

That code compiles and executes properly on both VC10, 11, and GCC. Edited by Ameise

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1336067215' post='4937155']
Function pointer != function object.
[/quote]

D'oh; I misread what you'd said.

Share this post


Link to post
Share on other sites
If it's any consolation, MSVC 2010 apparently doesn't supply the function pointer conversion, so I need to amend my statement to include verbiage along the lines of "with standard conforming compilers...".

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1336086159' post='4937245']
If it's any consolation, MSVC 2010 apparently doesn't supply the function pointer conversion, so I need to amend my statement to include verbiage along the lines of "with standard conforming compilers...".
[/quote]

To be honest, MSVC 2010 is before the C++11 standard was even finalized, and even now the C++11 standard as only just published a little while ago. So its not really appropriate to be complaining about it not being standards compliant with C++0x functionality when said standard didn't even exist.

That being said, the lack of a lambda => function pointer conversion for non-capture lambdas is kind of annoying, but not something I encounter overmuch, as I tend to avoid bare function pointers ANYWAYS.

Share this post


Link to post
Share on other sites
[quote name='SiCrane' timestamp='1336052848' post='4937096']
The type of a lambda function is a [i]unique anonymous[/i] functor type for every lambda function. Two separate lambdas that have the exact same definition in the exact same scope will have two separate types. As such, trying to get at the exact type of a lambda function is an exercise in futility. Just store the std::function by value.
[/quote]I'm probably going to get bashed - again - for stating my thoughts but... why not to just autobox them to an appropriate std::function at this point?
I mean, really, I feel that a bit rough. It's just me.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this