I can't speak to Virgil, never used it, but C++ features try their best to adhere to the principle that you don't pay for what you don't use. It's not perfect but pointer to member functions in C++ use the simplest implementation that is possible for that given functionality. In C# delegates can not be unbound from instance variables, they must be bound at the point of creation. In C++ that is not the case which makes C++ pointer to members much more flexible.
It looks like in Virgil you can have both bound and unbound pointers. You are welcome to correct me but I suspect it's implemented in such a way that you are always paying the cost of having a bound function pointer even if you only ever use it as an unbound function pointer. This would violate the principle I mentioned.
In C++ if you don't mind always paying that cost, you are welcome to use std::function and it will work just as it does in the example you gave:
In Virgil every first-class function is a represented as a fat pointer, i.e. two words[1], for both bound and unbound delegates. It's simple to understand, the syntax is clean, and it always works the way you expect it to. It doesn't create garbage and basically means you use two registers instead of one. The unbound version even does a proper virtual dispatch for you.
I understand C++'s intention, but it's doing no one any favors here and makes it really hard to build proper abstractions. Pointers to members are clunky, hard to use, and easy to screw up. AFAIU it's possible that a pointer to a member to be a single simple function pointer, but not guaranteed, and you shouldn't rely on it. It seems like a very bad tradeoff. std::function is considerably more heavyweight (and according to this https://stackoverflow.com/questions/13503511/sizeof-of-stdfu... might be arbitrarily large); i.e. it's slow in practice and people avoid it.
[1] For programs less than 4GB combined code and heap, the two pointers can be packed into a single 64-bit word (though the compiler doesn't do this currently).
>The unbound version even does a proper virtual dispatch for you--in C++ you can break class invariants by skipping a virtual dispatch by using a member pointer.
This is not true, the C++ version does the proper virtual dispatch.
>I understand C++'s intention, but it's doing no one any favors here and makes it really hard to build proper abstractions.
std::function is a perfectly fine abstraction built on-top of the lower level primitives if you don't care about always paying a performance penalty. As someone who writes performance sensitive code, I do care so I try to avoid that penalty.
Of course std::function can allocate arbitrarily large amounts of memory, so can Virgil's implementation. The sizeof(std::function) is always fixed, but because it can capture arbitrarily functions which themselves can carry arbitrarily large state, then so too must std::function also have the potential to allocate arbitrarily large amounts of memory.
In C++ people avoid std::function because as you said it's slow, and people tend to not use C++ for programs that can be slow, this goes back to the principle of not having to pay for what you don't use. In other languages you don't get that choice, you basically are required to pay the worst case cost even if you never use it.
It's almost never used. The delegate version is almost universally used.
> Even if the lambda were to inline the call, it still would be a completely different location in the executable image.
Not sure what you mean. Inlined code is not in a different location, it's right where one is executing! Also, optimizers are pretty darned good these days.
Both the member function and the lambda are their own symbols with their own machine code -- might not even be possible for them to be the same code due to ABI concerns.
Even if they're the same code, I don't think it's that easy for linkers to merge them?
Inlining can happen in several places - the front end, the optimizer, or the link step. The process of inlining removes the need for the symbol for the inlined function.
I recommend writing some code snippets, compile them with inlining on, and looking at the resulting assembler code.
Generally inlining can happen way earlier than linking. E.g. Virgil's compilation model doesn't have a linker at all, it's a whole-program compiler, and that works pretty well, even for 50KLOC programs.
For a trampoline to have no overhead, you need the call to the trampoline to be changed to a call to the underlying function.
That's not an optimization that can easily happen due to the traditional compilation model.
Even if inlining were to happen, you end up with bloat, and few compilers are able to merge similar code like this (which can only happen at link-time, obviously, since the functions might be in different translation units). This optimization is known as ICF, and is not commonly enabled.
In practice I don't think the inliner takes ICF into consideration when deciding whether to inline anyway, so you just end up calling a function that calls another function.
You're talking about C/C++'s compilation model and ABI and there are plenty of others. ICF is a hack to deal with C++'s naive template expansion. There are lots of other languages that don't work that way at all and don't need a linker optimization like that.
It looks like in Virgil you can have both bound and unbound pointers. You are welcome to correct me but I suspect it's implemented in such a way that you are always paying the cost of having a bound function pointer even if you only ever use it as an unbound function pointer. This would violate the principle I mentioned.
In C++ if you don't mind always paying that cost, you are welcome to use std::function and it will work just as it does in the example you gave: