&object->memfun
would be shorthand for the following lambda:
[object](params_of_memfun...)->return_type_of_memfun {
return object->memfun(std::fwd<>(params_of_memfun...));
}
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
sg14 is interested in being able to take a virtual function + object and resolve the specific function that would be called. We could use such a feature to do manual devirtualization and other efficiency optimizations.
Most recent discussion on std-proposals: https://groups.google.com/a/isocpp.org/d/topic/std-proposals/eltqBVyh5H0/discussion - should we move to there?On Monday, 26 October 2015 20:24:35 UTC, Nicol Bolas wrote:On Monday, October 26, 2015 at 2:54:54 PM UTC-4, Miro Knejp wrote:Am 26.10.2015 um 19:03 schrieb Nicol Bolas:
Which just strengthens his position that the "proper" way to do it is difficult to get right and suffers from boilerplate.On Monday, October 26, 2015 at 1:01:46 PM UTC-4, Viacheslav Usov wrote:I'm pretty sure this is missing a number of `std::forward` calls and `&&` syntax.
I wasn't contesting the need for the general idea. My problem was with his chosen form.
C++ does need some way to say "give me a functor that will call this function as if I typed that name." But this ought to be separate from binding to variables, since that's way too easy to get wrong.If you don't provide object binding, people are going to use std::bind or some partial applicator, which is just as easy to get wrong.
[]Class::Funcname; //Functor that calls the named member function, which takes as a first parameter a Class reference/pointer/object.
[d]d.Funcname; //Functor that calls the named member function, using a variable bound by lambda rules.
[]d.Funcname; //A compile error. You must explicitly bind the variable.
[]Funcname; //Functor calling a non-member function.
[this]Funcname; //Functor possibly calling a member function or non-member function, depending on the type of `this`.I'm curious as to how, exactly. A virtual call involves getting the vtable, followed by fetching the function pointer through an offset.
--
We already want overload syntax, for member functions and non-member functions. The obvious implementation is a lambda, so simply allow lambdas to do it cleanly, without the boilerplate:[]Class::Funcname; //Functor that calls the named member function, which takes as a first parameter a Class reference/pointer/object.
[d]d.Funcname; //Functor that calls the named member function, using a variable bound by lambda rules.
[]d.Funcname; //A compile error. You must explicitly bind the variable.
[]Funcname; //Functor calling a non-member function.
[this]Funcname; //Functor possibly calling a member function or non-member function, depending on the type of `this`.
We already want overload syntax, for member functions and non-member functions. The obvious implementation is a lambda, so simply allow lambdas to do it cleanly, without the boilerplate:[]Class::Funcname; //Functor that calls the named member function, which takes as a first parameter a Class reference/pointer/object.
[d]d.Funcname; //Functor that calls the named member function, using a variable bound by lambda rules.
[]d.Funcname; //A compile error. You must explicitly bind the variable.
[]Funcname; //Functor calling a non-member function.
[this]Funcname; //Functor possibly calling a member function or non-member function, depending on the type of `this`.How would we wrap static member functions with this?
I think this could be generalized further by introducing a placeholder syntax. Something like the following: * forwards one argument, and ... forwards any number, and it would not need to be at the end of the argument list.
We already want overload syntax, for member functions and non-member functions. The obvious implementation is a lambda, so simply allow lambdas to do it cleanly, without the boilerplate:[]Class::Funcname; //Functor that calls the named member function, which takes as a first parameter a Class reference/pointer/object.
[d]d.Funcname; //Functor that calls the named member function, using a variable bound by lambda rules.
[]d.Funcname; //A compile error. You must explicitly bind the variable.
[]Funcname; //Functor calling a non-member function.
[this]Funcname; //Functor possibly calling a member function or non-member function, depending on the type of `this`.
Proposal for address of bound function:
I want to give a more concrete proposal for how I think the address of bound function operation would work. These ideas are based on two very specific use cases, so I will state those up-front because it appears from reading people's comments that there are other use cases which I haven't given much consideration to. Note also, I will use the syntax (&object.fun) as example syntax. Other syntax may be more appropriate. I've just thrown this together for now, as I would like feedback before getting more specific/detailed.
Use case 1: "Native C++" callback functions:
C currently has a fairly standard callback function system, which consists of the following type of registration function:
typedef RetType(Callback*)(void* UserData, params…)
void RegisterCallback(Callback* CallbackFunc, void* UserData);
This works well for C where data/functions are not linked. However, in many cases C++ programs want UserData to point to an object, and callbackfunc to be a member function of that object. That is, we want to be able to have:
typedef RetType(*::MemCallback*)(params)
void RegisterCallback(MemCallback* CallbackMemberFunction);
which could be called using the following or similar syntax:
RegisterCallback(&object.memfunc)
I think that this could be described as the "native C++" equivalent to C-style callback functions. Note that it is possible for this to work without dynamic memory allocation and by utilizing only the memory space of 2 pointers for storing the function call. However, it is not possible with the current C++ spec.
Use case 2: Wrapping C-style callbacks
The second related use case is for wrapping C-style callback function syntax in the native C++ syntax. So if we are using a C library in C++, we should be able to have a wrapper to which we can pass RegisterCallback(&object.memfunc), and have it internally prepare a non-member function pointer and void* pointer which contain all of the necessary information to have a member function called by the callback function. In my proposal, this is possible and fairly efficient via the indirect use of stub functions.
Just a special note of consideration for this use case is that we cannot rely on the C library always passing the void* userdata argument as the first argument in a function or in a way that is compatible with the thiscall calling convention.
Problems with Current C++:
The current C++ spec has a number of problems, the most severe of which is the implementation of member function pointers. (Note I'm basing this on the MSVC implementation, but I believe these problems are common to GCC, etc.)
Problem 1:
There are 3 different and completely incompatible types of member function pointers based on the class of which the function is a member. If the class is purely singly inherited, the member pointer is implemented as a single pointer to the function code, which is something that is relatively easy to work with. However, if the class contains multiple inheritance or virtual inheritance in the hierarchy, then the member pointer additionally has information relating to adjusting the offset of the object pointer, and these three different structures are incompatible. In the case of callback functions, however, the object and function pointer are always supplied together. There should be no need to have different size structures when the object is known in advance.
Problem 2:
The second problem is that the current system is error prone. Take the following example for the case of trying to implement a member function callback system:
template<class Obj_t, class MemObj_t>
void RegisterCallback(Obj_t* obj, void (MemObj_t::mem_fun*)(params…))
class Base1 {
};
class Derived1 : Base1 {
virtual void DoStuff();
};
class Base2 {
virtual void DoStuff();
};
class Derived2 : Base2 {
virtual void DoStuff();
};
Base1 b1;
RegisterCallback(&b1, &Derived1::DoStuff);
Base2 b2;
RegisterCallback(&b2, &Derived2::DoStuff);
The first example is broken, but we do not get any compile-time errors.
The second example is fine, and is the reason we need to allow different classes in the object and member function definitions.
Problem 3:
The third problem is related to passing virtual functions to C-style callback systems. It is basically impossible to do without implementing stub objects that store additional information.
Notation:
I first want to introduce special notation I want to use to describe the proposal.
THISCALL(func*, object*, params…)
This is pseudo-code to represent the compilation code that actually makes a function call given a pointer to the actual function code, and the pointer to the this object. Note that the object* pointer here is required to be already offset-adjusted to point to the correct location within the inheritance hierarchy. It is expected to be a void* at runtime.
func* VTABLOOKUP(object*, index)
This is pseudo-code for taking an object (as before, already offset-adjusted to point to the correct vtable for the function being called) and the index within that table, and returning a pointer to the function that will actually run. The function returned from the vtable will take an object* as the this pointer without adjustment because of the way vtables are constructed.
Returned structure:
Because of the differences between virtual member functions and non-virtual member functions, the structures returned by (&obj.fun) will need to be slightly different but only in terms of type information. The actual data stored in the structures will be identically usable.
For virtual functions, &obj.fun returns:
template<int virtindex, class object_t, class RetType(Params…)>
struct VirtualMemFunc {
object_t* callobject;
func* functionentrypoint;
// For "native C++" callback
RetType operator(Params…) {
return THISCALL(functionentrypoint, callobject, Params…);
}
// For "C-style" callback
typedef RetType(Param0Callback*)(void* object, Params…);
typedef RetType(Param1Callback*)(Param0_t Param0, void* object, Params…);
Param0Callback* GetCCallbackStub0() {
return &VirtualMemFuncCStub<virtindex, RetType(Params…)>::Stub0;
}
Param1Callback* GetCCallbackStub1() {
return &VirtualMemFuncCStub<virtindex, RetType(Params…)>::Stub1;
}
void* GetCCallbackUserData() {
return (void*) callobject;
}
};
template<int virtindex, class RetType(Params…)>
struct VirtualMemFuncCStub {
static RetType Stub0(void* UserData, Params…) {
return THISCALL(VTABLOOKUP(UserData, virtindex), UserData, Params…);
}
static RetType Stub1(Param0_t Param0, void* UserData, Params…) {
return THISCALL(VTABLOOKUP(UserData, virtindex), UserData, Param0, Params…);
}
}
For non-virtual member functions, &obj.fun returns:
template<uint64_t funcaddr, class object_t, class RetType(Params…)>
struct NonVirtualMemFunc {
object_t* callobject;
func* functionentrypoint;
// For "native C++" callback
RetType operator(Params…) {
return THISCALL(functionentrypoint, callobject, Params…);
}
// For "C-style" callback
typedef RetType(Param0Callback*)(void* object, Params…);
typedef RetType(Param1Callback*)(Param0_t Param0, void* object, Params…);
Param0Callback* GetCCallbackStub0() {
return &NonVirtualMemFuncCStub<funcaddr, RetType(Params…)>::Stub0;
}
Param1Callback* GetCCallbackStub1() {
return &NonVirtualMemFuncCStub<funcaddr, RetType(Params…)>::Stub1;
}
void* GetCCallbackUserData() {
return (void*) callobject;
}
};
template<uint64_t funcaddr, class RetType(Params…)>
struct NonVirtualMemFuncCStub {
static RetType Stub0(void* UserData, Params…) {
return THISCALL(funcaddr, UserData, Params…);
}
static RetType Stub1(Param0_t Param0, void* UserData, Params…) {
return THISCALL(funcaddr, UserData, Param0, Params…);
}
}
Notes on Abstraction/Reification:
The basic idea is that any offset-adjustment is performed at the time that (&obj.fun) is called. That is, for the following:
class Base1 {
int dat;
}
class Base2 {
virtual DoStuff() {};
}
class Derived : Base1, Base2 {
};
Derived d;
auto PtrToMemFun = (&d.DoStuff);
In this case, at compile time the compiler sees that DoStuff is defined in Base2. Thus, the structure returned is:
VirtualMemFunc<0, Base2, RetType(Params…)>
That is, the object stored in the structure points to Base2, not to Derived. There is no further need for pointer adjustment, and so the type information is no longer needed in terms of actually calling the function. The same is true of non-virtual member functions. That is, we can use a common base class:
template<class RetType(Params…)>
struct CommonMemFunc {
void* callobject;
void* functionentrypoint;
RetType operator(Params…) {
return THISCALL(functionentrypoint, callobject, Params…);
}
}
For C-style callbacks, we have a similar situation. We can extract the function call and void* UserData pointers to get a common set of data.
Usage:
For "native C++" callbacks, it would be sufficient to define:
void RegisterCallback(CommonMemFunc<RetType(Params…)> callbackfunc) {
}
The structure is purely a set of 2 pointers that do not need type information.
For C-style callback wrappers things are slightly more complicated since we need to have access the reified structures to get the correct stub function:
template<int virtindex, class object_t>
void RegisterCallback(VirtualMemFunc<virtindex, object_t, RetType(Params…)> callbackfunc) {
CCodeRegisterCallback(callbackfunc.GetCCallbackStub0(), callbackfunc.GetCCallbackUserData());
}
template<uint64_t funcaddr, class object_t>
void RegisterCallback(NonVirtualMemFunc<funcaddr, object_t, RetType(Params…)> callbackfunc) {
CCodeRegisterCallback(callbackfunc.GetCCallbackStub0(), callbackfunc.GetCCallbackUserData());
}
OK, this has gotten quite long. I hope this gives an idea of what I was looking for. Suggestions/questions welcome.