Hacker News new | past | comments | ask | show | jobs | submit login

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.

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. They are more complex than plain pointers because they will still do proper virtual dispatching in the presence of multiple inheritance which requires more machinery than a plain function pointer, but this has absolutely nothing to do with keeping a pointer to an instance/this.


You are correct, what I wrote is unclear. See my other posts in this thread.


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

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

https://en.wikipedia.org/wiki/Digital_Mars


No it's just incorrect and no amount of pandering to his authority will change that.

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.


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


[flagged]


I've never been on internet relay chat.


ok must be an imposter my bad.


C++ pointers-to-members have no standard structure and are implementation defined. But in no implementations they have pointers to instances inside them. If that was the case Why would you need an instance (again) pointer/reference to use pointer-to-member? In most implementations they just encode an offset


The pointer to instance part is the `this` pointer.

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


> The pointer to instance part is the `this` pointer.

What do you mean? The this pointer isn’t computed until you try to call the pointer-to-member-function.


>> The pointer to instance part is the `this` pointer.

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


Pointer to what, exactly? It doesn't make sense for it to be an object instance pointer, because you can use the same pointer to member on several different objects instances. If one instance's pointer was embedded in the pointer-to-member, it wouldn't work for other instances.

I have no idea what the format of a pointer-to-member is. It sure looks like a small closure.


See https://news.ycombinator.com/item?id=39812834 for a more complete explanation.


If I follow that link and end up here:

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.


> But you said

Yeah, I miswrote it. Hence the link to my article about it.


But again, ‘this’ pointer is not in any way stored inside pointer-to-member. Caller has to provide one


Delphi captures the pointer-to-instance and pointer-to-function concept too. It uses them for event handlers: delegating behaviour to other classes. To do that, method pointers are often a 'fat pointer': the pair of object instance and function.

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


C# calls them delegates too, as does Virgil. Virgil uses the fat pointer trick so to avoid any heap allocations. In Virgil,

    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.


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:

    std::function<ReturnType (Arg1, Arg2, ...)>


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.

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


> In C++ that is not the case which makes C++ pointer to members much more flexible.

Here's an article describing how to achieve the same effect in D:

https://www.digitalmars.com/articles/b68.html


Adding a trampoline means making the calls slower.

Even if the lambda were to inline the call, it still would be a completely different location in the executable image.


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.


That's not the problem I pointed out at all.

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.


> due to the traditional compilation model.

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.


ICF is important to fold equivalent lambdas as well.

All compiled languages virtually use the C model of doing things, often including its FFI.


Also, FWIW, Virgil doesn't have "pointer to member" for fields; it does have a first-class getter for fields, but not a setter:

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


I like how delegates are easy in Python.

  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)




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: