Assigning lambdas

Started by
26 comments, last by Krohm 11 years, 11 months ago
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 functional programming as silver bullet 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 his article on functional programming. 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:

#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;
}

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?

Previously "Krohm"

Advertisement
[s]Works fine in GCC. Perhaps a compiler bug?[/s]

[edit: rather, it appears to work fine, but is actually incorrect. Read below for further explanation]
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. lines is not an instance of std::function, but an instance of Visual C++'s built-in lambda type. You are then passing a temporary object 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.
Visual Studio, even the latest 2011 beta is next to useless 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 different comparision (note the partial support, even for VS12.0, almost everything is way behind GCC).

Visual Studio, even the latest 2011 beta is next to useless for C++0x.

2010 is not even in contest.

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


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.


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


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

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

PrintFunc const& func; // (2)

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


The fact that GCC works is actually surprising.
...
This still isn't a bug in VS10 or 11; they are both behaving properly.
Formally problem solved: so much for designing a language that now has two types of lambdas.
What is a correct syntax?


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.
I suppose I will change C++11 next to useless 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 expected thing - is formally wrong?
This whole thing starts to smell like nonsense to me.

Previously "Krohm"


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.

This line:
Callbacks one(lines); // (1)
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):
Callbacks one(PrintFunc(lines)); // (1)

by changing this line:
PrintFunc const& func; // (2)
to
PrintFunc func; // (2)
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:


#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;
}

In time the project grows, the ignorance of its devs it shows, with many a convoluted function, it plunges into deep compunction, the price of failure is high, Washu's mirth is nigh.

. But anyway, what's wrong and risky? I took the syntax from Washu's article.[/quote]

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

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

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


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

  1. The language/compiler supports lambdas properly
  2. Or it does not - like it's the case here

Now, it's clear at this point what was going on, so the rest is mostly rant. Or maybe not.


Works fine in GCC. Perhaps a compiler bug?
I mean seriously... we are bashing even the expected thing just because we want to be formally correct?
Because in my opinion what GCC is doing is The Right Thing 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.

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. But heck, we are talking about function pointers!

struct Callbacks {
void (*func)(void); // (2)
Callbacks(void (*func)(void)) : func(call) { }
};
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 -

it was clear since start VC2010's C++11 was not properly implemented but I didn't expect it so bad - 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. Works fine in GCC. 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 lambda construct 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 my hate goes to Microsoft, 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.


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

Previously "Krohm"

This topic is closed to new replies.

Advertisement