Jump to content

  • Log In with Google      Sign In   
  • Create Account


Assigning lambdas


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
27 replies to this topic

#1 Krohm   Crossbones+   -  Reputation: 3052

Like
0Likes
Like

Posted 01 May 2012 - 03:23 AM

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 MulticastEvent::AddHandler or by value, by storing in std::vector<EventSignature>.
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?

Attached Thumbnails

  • lambda_const_ref.png


Sponsor:

#2 rip-off   Moderators   -  Reputation: 8120

Like
0Likes
Like

Posted 01 May 2012 - 03:57 AM

Works fine in GCC. Perhaps a compiler bug?

[edit: rather, it appears to work fine, but is actually incorrect. Read below for further explanation]

Edited by rip-off, 03 May 2012 - 03:26 AM.


#3 Ameise   Members   -  Reputation: 711

Like
1Likes
Like

Posted 01 May 2012 - 11:38 AM

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.

#4 Antheus   Members   -  Reputation: 2397

Like
0Likes
Like

Posted 01 May 2012 - 11:53 AM

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

Edited by Antheus, 01 May 2012 - 11:55 AM.


#5 Ameise   Members   -  Reputation: 711

Like
3Likes
Like

Posted 01 May 2012 - 11:55 AM

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.

#6 Antheus   Members   -  Reputation: 2397

Like
0Likes
Like

Posted 01 May 2012 - 12:49 PM

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.

#7 Krohm   Crossbones+   -  Reputation: 3052

Like
-1Likes
Like

Posted 02 May 2012 - 01:25 AM

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

#8 Washu   Senior Moderators   -  Reputation: 5003

Like
1Likes
Like

Posted 02 May 2012 - 01:59 AM

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

Edited by Washu, 02 May 2012 - 02:02 AM.

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.
ScapeCode - Blog | SlimDX


#9 Antheus   Members   -  Reputation: 2397

Like
0Likes
Like

Posted 02 May 2012 - 07:37 AM

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


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.

#10 Krohm   Crossbones+   -  Reputation: 3052

Like
0Likes
Like

Posted 02 May 2012 - 11:52 PM

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
  • The language/compiler supports lambdas properly
  • 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 operator(), or by using std::function, 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 for loops might emit the same code as some while 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 typedef char sbyte 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.

Attached Thumbnails

  • FuncCallTree.png

Edited by Krohm, 02 May 2012 - 11:56 PM.


#11 Washu   Senior Moderators   -  Reputation: 5003

Like
0Likes
Like

Posted 02 May 2012 - 11:56 PM

...

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.

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.
ScapeCode - Blog | SlimDX


#12 KidsLoveSatan   Members   -  Reputation: 501

Like
0Likes
Like

Posted 03 May 2012 - 02:36 AM

As others have pointed out, lambas are not an instance of std::function<>. Microsoft in this instance have done nothing wrong.

#13 rip-off   Moderators   -  Reputation: 8120

Like
0Likes
Like

Posted 03 May 2012 - 03:25 AM


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.

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, "Appears to work fine in GCC".

#14 Antheus   Members   -  Reputation: 2397

Like
0Likes
Like

Posted 03 May 2012 - 05:37 AM

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.


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

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

const char * s;
foo(s);
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:
foo(std::string(s)); // which is potentially ambiguous as function call
// so
std::string temp(s);
foo(temp);
// vs
foo("Hello World");



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


They can be, but don't produce desirable result. Simple example, using pointers to make it clearer:
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

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, 03 May 2012 - 05:39 AM.


#15 Krohm   Crossbones+   -  Reputation: 3052

Like
0Likes
Like

Posted 03 May 2012 - 07:32 AM

No. I don't understand. And clearly, I cannot even explain. To quote myself:

This relies on a subtle detail: the fact that push_back performs a value copy.

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
typedef [](){}->void PrintFunc;
But it does not appear to work. I think I've seen somewhere something involved instantiating a function and capturing its type. Is std::function 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.

#16 SiCrane   Moderators   -  Reputation: 9566

Like
0Likes
Like

Posted 03 May 2012 - 07:47 AM

The type of a lambda function is a unique anonymous 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.

#17 Cornstalks   Crossbones+   -  Reputation: 6974

Like
0Likes
Like

Posted 03 May 2012 - 07:59 AM

The type of a lambda function is a unique anonymous 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.

With that in mind, it made me curious if
auto lines = []() { cout<<"- - - - - -"<<endl; };
auto dots  = []() { cout<<". . . . . ."<<endl; };
lines = dots;
works. I don't have a compiler on my current machine to test if the assignment gives a compile time error. 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.

[edit]

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

However, a normal function pointer worked:
#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();
}

But yes, I would just go with a std::function.

Edited by Cornstalks, 03 May 2012 - 08:14 AM.

[ I was ninja'd 71 times before I stopped counting a long time ago ] [ f.k.a. MikeTacular ] [ My Blog ] [ SWFer: Gaplessly looped MP3s in your Flash games ]

#18 SiCrane   Moderators   -  Reputation: 9566

Like
0Likes
Like

Posted 03 May 2012 - 11:25 AM

However, a normal function pointer worked:

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.

#19 Ameise   Members   -  Reputation: 711

Like
0Likes
Like

Posted 03 May 2012 - 11:41 AM


However, a normal function pointer worked:

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.


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

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

Edited by Ameise, 03 May 2012 - 11:43 AM.


#20 SiCrane   Moderators   -  Reputation: 9566

Like
0Likes
Like

Posted 03 May 2012 - 11:46 AM

Function pointer != function object.




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS