void fkn1() {
cout << 0 << endl;
}
void fkn1(int x) {
cout << x << endl;
}
void fkn2(int x) {
cout << 2*x << endl;
}
int main() {
// Specify both function name, signature and make the call.
// or, deduce function address from signature and name, call address.
// This is overload resolution in standard language.
fkn1(1);
}
void fkn1() {
cout << 0 << endl;
}
void fkn1(int x) {
cout << x << endl;
}
void fkn2(int x) {
cout << 2*x << endl;
}
int main() {
// Specify signature, part of the type system.
using Fkn = void(*)(int);
// make function pointer.
Fkn fkn = nullptr;
// Calculate address from name together with signature.
// Overload resolution is performed. Assign to pointer.
fkn = &fkn1;
// Do the call. Address and argument values
// needed at the call site. No resolution done.
fkn(2);
}
Fkn pointer construction
Req: Fkn signature
Assign address
or address
Fkn call:
Req: signature + arguments
Fkn pointer destruction
Time
struct C1 {
static void fkn1(C1* obj) {
cout << 1 << endl;
}
static void fkn2(C1* obj) {
cout << 2 << endl;
}
};
int main() {
// static member function works as free functions. Just
// declared within a class and have the class name as part of their name.
C1::fkn1(nullptr);
// Specify signature when setting up a function pointer type.
using Fkn = void(*)(C1*);
// Use name (including class name) with signature to extract address.
Fkn fkn = &C1::fkn1;
// and finally call.
C1 c1;
fkn(&c1);
}
Function pointer to class static functions
struct C1 {
static void fkn1(C1 const* c1) {
cout << 1 << endl;
}
void member() const {
cout << x << endl;
}
};
int main() {
// Static function signature as before.
using Fkn = void (*)(C1 const*);
Fkn freeFkn = &C1::fkn1;
// Member function
// Good analogy: the object pointer is a hidden first argument.
// Note: object pointer type is part of the signature,
// just as first argument in free function pointer.
using MemberFkn = void (C1::*)() const;
MemberFkn mFkn = &C1::member;
C1 c1;
// Call on fkn pointers. Require fkn address and signature.
freeFkn(&c1);
// Note the quirky syntax. Required.
(c1.*mFkn)();
}
Member function pointers
struct B1 {
virtual void member() {
cout << 1 << endl;
}
};
struct D1 : public B1 {
void member() {
cout << 2 << endl;
}
};
int main() {
D1 d1;
// Specify signature to base class member function.
using MemPtr = void (B1::*)();
// Get base member function pointer.
MemPtr ptr = &B1::member;
// Get a base pointer.
B1* b_ptr = &d1;
// Member function special power: Do dynamic dispatch. Calls D1::member.
(b_ptr->*ptr)();
}
Member pointer secret power: dynamic dispatch
struct F1 {
F1(int m) : member(m) {}
void operator()(int i) {
cout << i + member << endl;
}
void operator()() {
cout << m << endl;
}
int member;
};
int main() {
// Functors are ordinary classes
F1 f1{4};
// That can be called as functions.
f1(3);
f1();
// Can create references and pointers:
F1* ptr = &f1;
F1& ref = f1;
// And call through them.
(*ptr)();
ref();
}
// However there is no special function pointer syntax for functors.
Functors: objects with operator()
struct F1 {
F1(int m) : member(m) {}
void operator()(int i) {
cout << i + member << endl;
}
int member;
};
int main() {
// Functors are ordinary classes
int m = 4;
F1 f1{4};
auto lambda = [m](int i) -> void { cout << i + m << endl; }
// Lambda is defined to construct a templated functor.
// F1 is similar to the functor class made from lambda.
f1(3);
lambda(3);
}
Lambdas
struct F1 {
F1(int m) : member(m) {}
void operator()(int i) {
cout << i + member << endl;
}
int member;
};
int main() {
F1 f1(4);
// Store a copy of the functor.
std::function<void(int)> fknStore = f1;
// Call the function store.
// Note: Do not need to know the type of the functor, only signature.
fknStore(3);
// std::function implement type-erasure to allow storing full type
// but only exposing signature at call time.
}
std::function and type erasure
// Common C function solution.
// Driver code. Offers a callback without knowing callers type.
typedef void (*DrvCB)(void* ctx, int cbArg);
void drv_registerCB(DrvCB cb, void* context);
// User of the driver:
struct UserData { /* ... */ };
void userCodeInit(struct UserData* ud)
{
// Init ctx ...
drv_registerCB(drvCB, (void*)ud);
// ...
}
static void drvCB(void* ctx, int cbArg)
{
// Required cast from void to regain context type.
struct UserData* ud = (struct UserData*)ctx;
// Do callback processing...
}
// Direct old style C++ conversion.
class Driver {
public:
using DrvCB = void (*)(void* ctx, int cbArg);
void registerCB(DrvCB cb_, void* ctx_) {
cb = cb_;
cbCtx = ctx_;
}
private:
DrvCB cb;
void* cbCtx;
};
class UserClass
{
public:
UserClass(Driver* drv_p)
{
drv_p->registerCB(drvCB_, (void*)this);
// ...
}
private:
static void drvCB_(void* ctx, int arg) {
static_cast<UserClass*>(ctx)->drvCB(arg);
}
void drvCB(int arg) {
// Actual CB processing
}
}
A std::function replacement
class Delegate
{
public:
using AdapterFkn = void (*)(void*ctx,int arg);
// New set method. Handles member function to object.
template <class T, void (T::*memFkn)(int arg)>
void set(T& object)
{
cb = &doMemberCB<T, memFkn>;
ctx = static_cast<void*>(&object);
}
void operator()(int t) {
cb(t);
}
private:
// Handler function for memeber callbacks. Same internal signature
// Other ways to unpack the template parameters.
template <class T, void (T::*memFkn)(int)>
inline static void doMemberCB(void* o, int arg)
{
T* obj = static_cast<T*>(o);
((*obj).*(memFkn))(arg);
}
AdapterFkn cb = nullptr;
void* ctx = nullptr;
};
Delegate handling of a member function
class Driver {
public:
Delegate& regCB();
private:
void isrProc() {
// ...
del(value);
}
// Note: no type information about the registered caller. It
// is handled in the 'set' function template. It does not leak
// to the delegate class. (Later, will be: delegate<void(int)>)
Delegate del;
};
class UserClass
{
public:
UserClass(Driver& drv)
{
drv.regCB().set<UserClass, &UserClass::drvCB>(*this);
// ...
}
private:
// Trampoline fkn now in delegate. Only need memeber fkn.
void drvCB(int arg) {
// Actual CB processing
}
}
Use of delegate
class Delegate
{
public:
using AdapterFkn = void (*)(void*ctx,int arg);
template<typename T>
void setFunctor(T& f) {
cb = &adaptFunctor<T>;
ctx = static_cast<void*>(&t);
}
void operator()(int t) {
cb(ctx, t);
}
private:
template<class T>
static void adaptFunctor(void* ctx_, int arg) {
(*static_cast<T*>(ctx_))(arg);
}
AdapterFkn cb = nullptr;
void* ctx = nullptr;
};
Delegate handling a functor
#include "delegate/delegate.hpp"
struct Packet { /* ... */ };
class Driver {
public:
Driver() = default;
using RxDelegate = delegate<void(const Packet&)>;
RxDelegate& registerRx() { return packetRx; } ; // Either this,
void setRx(RxDelegate rx) { packetRx = rx; }; // Or this.
private:
RxDelegate packetRx;
};
class ProtocolRx {
public:
ProtocolRx(Driver& drv) {
drv.registerRx().set<
ProtocolRx, &ProtocolRx::rxPacket>(*this); // Either this,
drv.setRx(Driver::RxDelegate::make<
ProtocolRx, &ProtocolRx::rxPacket>(*this); // Or this
drv.registerRx().set<&ProtocolRx::rxPacket>(*this); // C++17
}
private:
void rxPacket(Packet const& packet)
{ /* ... */ }
};
#include "delegate/delegate.hpp"
struct TStruct {
int member(int i) { return i + 1; }
int cmember(int i) const { return i + 2; }
};
TEST(delegate, Member_intermediate_storage) {
TStruct ts;
const TStruct cts;
delegate<int(int)> del;
del.set<TStruct, &TStruct::member>(ts);
int res = del(1);
EXPECT_EQ(res, 2);
// Must not compile. Need const member for const object.
// del.set<TStruct, &TStruct::member>(cts);
del.set<TStruct, &TStruct::cmember>(ts);
res = del(1);
EXPECT_EQ(res, 3);
del.set<TStruct, &TStruct::cmember>(cts);
res = del(1);
EXPECT_EQ(res, 3);
// Must not compile. Do not allow storing pointer to temporary.
// del.set<TStruct, &TStruct::member>(TStruct{});
// del.set<TStruct, &TStruct::cmember>(TStruct{});
}
Const correctness
#include "delegate/delegate.hpp"
TEST(delegate, construction1) {
delegate<void()> del;
delegate<void()> del2;
EXPECT_TRUE(del.null());
EXPECT_FALSE(del);
EXPECT_TRUE(del == nullptr); // And variants.
EXPECT_TRUE(del == del2);
EXPECT_TRUE(del.equal(del2));
EXPECT_FALSE(del.less(del2));
// Ok to call null delegate. Call function that do nothing and
// return default constructed return value.
del();
}
Default construct and safe nullptr call
#include "delegate/delegate.hpp"
// Base, Derived defined as expected, virtual memb, cmemb.
TEST(delegate, test_virtual_dispatch)
{
Derived d;
delegate<int(int)> del;
Base& b = d;
del.set<Base, &Base::memb>(b);
// Derived::memb called.
del(1);
const Base& cb = d;
del.set<Base, &Base::cmemb>(cb);
// Derived::cmemb called.
del(1);
}
Available at:
https://github.com/rosbacke/delegate