C++ left arrow operator (2016)
atnnn.com
atnnn.com
D calls these delegates, and generalizes it to being a pair consisting of a "context pointer" and a "pointer to a function". The neato thing is these do not have to be struct or class member functions. They can be nested functions, where the the "context pointer" is a pointer to the stack frame of the caller. I.e., a pointer to the closure.
Hence, these become lambdas.
Lamdas and pointers-to-nested functions are completely interchangeable with pointers to members. The caller does not know the difference.
In fact, lambdas are far, far more commonly used in D than pointers-to-members.
> This isn't correct. C++ pointer to member's do not carry any instance information.
Mr. Bright's description is correct from the perspective of pointer-to-member's use, not when declared nor instantiated.
Given Mr. Bright's role in creating Zortech C, Zortech C++, amongst other compilers, and having used the two specifically mentioned personally, I believe he has a proven understanding of C/C++ compiler implementations.
Since one can interpret Mr. Bright's statement in the form of usage or definition, the former seems most applicable when viewed in the context of the remaining text.
People make mistakes and this is one of them, it's not the end of the world. Avoid the temptation to believe false information just because you happen to worship someone, it reflects poorly on your ability to critically judge information.
Ad hominem attacks are the hallmark of insecurity and your expression of same reeks of pompousness reserved for the most arrogant I have had the displeasure to engage.
You know nothing about me, nor my background.
I hope you take time to reflect on what you wrote above and choose to engage with others differently in the future.
> C++ pointers-to-members have no standard structure and are implementation defined.
A missed opportunity, though every C++ compiler I've examined did it the same way.
What do you mean? The this pointer isn’t computed until you try to call the pointer-to-member-function.
> What do you mean? The this pointer isn’t computed until you try to call the pointer-to-member-function.
Think of it from a compiler writer's perspective.
The implicit parameter when using a pointer-to-member function is the function pointer itself. The `this` (instance) pointer must be passed explicitly when invoking it (along with whatever other parameters the function requires).
Ergo:
>> The pointer to instance part is the `this` pointer.
I have no idea what the format of a pointer-to-member is. It sure looks like a small closure.
https://www.digitalmars.com/articles/b68.html
I find:
> C++ and D diverge here. D has the notion of a delegate, aka “fat pointer”, which is a pair consisting of a pointer to the member function and a pointer to the ‘this’ object. The virtualness of the member function was resolved at the point where the address of the member function was taken
…
> Alternatively, C++ has the notion of a member function pointer, which is independent of whatever object is used to call it. Such an object is provided when the member function is called
Which sounds right. But you said:
> The C++ pointer-to-member is a fairly confusing concept. What it actually is is a pair - a pointer to the instance of the struct, and a pointer to a function in that struct.
Which is neither consistent with that text nor with how C++ works. The D concept of a pointer to member is the fat pointer that encodes this. C++’s is the horrible lambda-ish thing that can find the method starting with any compatible this pointer. Yuck.
Yeah, I miswrote it. Hence the link to my article about it.
type TMethodPtr = procedure(x : Integer) of object; // 'of object' means an OO method pointer, not procedural
var p : TMethodPtr = foo.bar; // Captures both foo and bar
p(4); // Calls foo.bar(4)
Fun: you can use them in C++ too in C++Builder's dialect via the rather ugly syntax, void(__closure * myClosure)(int); // myClosure can point to a method taking an int, returning void
myClosure = pObj->func; // Assigng myClosure; this captures both pObj and the address of func
myClosure(4); // Call it: this calls pObj->func(4)
Syntax aside, they're neat because they match type safety by the method signature not by the type of the object on which they're called (which lets yu use them to delegate to any classes, not just ones inheriting from specific ancestors.) This bypasses contravariance constraints too.More info with a discussion on C++: https://www.codeproject.com/Articles/44874/C-Delegates-and-B...
class C {
def m() -> int { return 33; }
}
var x = C.new(); // allocate a new C
var y: void -> int = x.m; // delegate bound to x and m
var z: C -> int = C.m; // C.m method is first-class, takes an object
var t = z(x); // equivalent to x.m();
I never understood why C++ and other languages had such ugly syntax for such obvious concepts.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:
std::function<ReturnType (Arg1, Arg2, ...)>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).
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.
>std::function is considerably more heavyweight (and according to this https://stackoverflow.com/questions/13503511/sizeof-of-stdfu... might be arbitrarily large);
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.
Here's an article describing how to achieve the same effect in D:
Even if the lambda were to inline the call, it still would be a completely different location in the executable image.
> 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.
Even if they're the same code, I don't think it's that easy for linkers to merge them?
I recommend writing some code snippets, compile them with inlining on, and looking at the resulting assembler code.
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.
All compiled languages virtually use the C model of doing things, often including its FFI.
class C {
var f: int;
}
var x = C.new();
var g: C -> int = C.f; // C.f is a getter method for f
var z: int = g(x); del1 = someobject.some_method_without_parentheses
del2 = someotherobject.other_method
del2()
del1()
And the other way del1 = class1.method
del2 = class2.other_method
del1(obj_of_class1)
del2(obj_of_class2) delete this;
https://isocpp.org/wiki/faq/freestore-mgmt#delete-this struct foo {
atomic<int> refcount;
// other stuff
};
You can pass around a pointer, COM-style, and call ->Release as needed. But you can be a lot less error-prone by passing around a smart pointer that understands the intrusive refcount and handles releasing. At which point you get something like 'delete ptr' in a destructor, not 'delete this' in a Release function.That approach requires client and server to be written in the same language, use the same memory allocator, and same compiler. For COM objects, often all of these are false.
For example, C#, PowerShell or VBScript code can call IUnknown.Release() method of C++ implemented COM objects, which will cause C++ code to deallocate the memory. However, these higher level languages can’t directly delete C++ objects: they know nothing about C++ runtime, or C runtime.
this->~MyClass();
new (this) MyClass();
I have unironically used thiswhile (x < — - 10) { … }
In fact, you can make arrows longer:
while (x <— — — — - 10)
And you can make thicker arrows with plus signs:
while (x ++++> 10)
(hmmm… the HN text formatter is fighting me. Those are all minus signs)
Not code --
Code -- (“ Code --”)Returns x+1 or x-1 depending on the direction the tadpole swims (-~x to increment x and or ~-x to decrement x). For a short moment I believed this was a real thing, because my programming font has a special ligature for ~- and -~.
Cleverness is a register that is easy to overflow, and too many don't have to good taste to avoid doing so.
Give me names every time.
Doing linear algebra without any operator overloading is just unreadable
symbols and names are merely suggestive mnemonic devices. They can’t ever specify actual behaviors.
Accept it and supplement with English. That’s what math does. You have to read the context.
> optimized to tersness
That is a form of readability. If you think there is a conflict you need to explain mathematicians wouldn’t care about readability.
The worst is probably the original idea to overload the bit shift operators for stream I/O, something which never really caught on outside the standard library.
The code was a simulation of a parallel system with multiple services that sent messages between them, or something like that. Instead of using serviceA.send("hello",serviceB) you had something like serviceA >> "hello" >> serviceB.
I really loved that (my classmates not so much)
SinOsc b => Gain g => BiQuad f => dac;
https://chuck.stanford.edu/doc/language/oper.html#chuckthrow exceptionC() << "error code: " << t;
I often found myself having to format error strings for exceptions, so I thought I could just do it like cout in one line. I know now this is bad for i18n strings.
throw exceptionC("error code: ", t);
?Ever since perfect forwarding I've been using this pattern to get out of arrow hell:
template <typename Arg, typename...Args>
string to_string(Arg &&arg, Args &&...args) {
stringstream buf;
buf << arg;
((buf << std::forward<Args>(args)), ...);
return buf.str();
}[0] https://www.boost.org/doc/libs/1_78_0/libs/spirit/doc/html/s...
For example how would you implement std::function without overloaded operator()?
Also lambdas are defined in term of structs with overloaded operator ().
Without overloading, the standard could still ad-hoc define the specifications of lambdas and std::function, std:: ref, etc, but the language would be worse off.
To be mildly pedantic, printing in a destructor is horrible practice because stdout/stderr might be pipes or sockets and writing might fail. It's a really bad idea to do anything in a destructor that effects anything but the class being destroyed for those reasons, and when you get an error, it shows up as an opaque exception or trace or hang at the end of a scope instead of where it actually mattered.
MFC extends this idea to network programming, allowing the use of shift operators to send and receive data.
In the case of ostream and operator<<, this pattern reduces the number of intermediate objects that would otherwise be constructed.
If you object to iostream on religious or stylistic grounds, there's always fmt which is more like Go or Python string interpolation.[0]
No I object to it on usability grounds. It took more than 30 years for the committee to admit it but C++ now has typesafe std::print/std::format finally, after admonishing programmers for using `printf` in C.
Iostreams have a lot of issues, but overloading<< is a very minor one.
I'm on the permissive side (Haskell and Lenseful Haskell at that!), but make it consistent and principled and it's usually fine.