creating hash (or list) of member functions
-
@mzimmers said:
If I use a list, what does the call to the update method look like?
For example like this:
class Timer : public QObject { Q_OBJECT QList<std::function<void()>> clients; int client_index = -1; public: template<typename T> void registerClient(T* client, void(T::*cs)()) { clients.push_back(std::bind(cs, client)); } void notifyTimer() { if (!clients.isEmpty()) { if (++client_index >= clients.size()) client_index = 0; clients[client_index](); } } };
and you register clients like this:
timer.registerClient(clock1, &Clock::update); timer.registerClient(clock2, &Clock::update); ...
@Chris-Kawa yeah, I'd actually gotten something working, but I like yours better, because it accepts the callback as an argument, rather than hardcoding it in the timer.
I think this topic is closed, but I wonder if you could give me an explanation for the use of the std::bind. The list is just a list of QObjects; how does this "attach" the callback function?
Thanks!
-
M mzimmers has marked this topic as solved on
-
@Chris-Kawa yeah, I'd actually gotten something working, but I like yours better, because it accepts the callback as an argument, rather than hardcoding it in the timer.
I think this topic is closed, but I wonder if you could give me an explanation for the use of the std::bind. The list is just a list of QObjects; how does this "attach" the callback function?
Thanks!
@mzimmers In my example it's not a list of QObjects. It's a list of std::function objects.
Clock::update()
is a class member function, so to call it you need an instance of that class i.e.instance->update()
. The way class member functions work is that they really are just regular functions that have a hidden implicitthis
parameter, so in effect it's likeClock::update(instance)
.
std::bind
, as the name suggests, creates a callable object that binds a functor with a parameter, so you can call it as if there was no parameter.
The way to think about it is thatstd::bind(cs, client)
creates a struct with the operator(), something like this:struct Something { Clock* client; void operator()() { client->update(); } }
so it turns a class member function with hidden
this
parameter into something that can be called without parameters. Then I just store it in a std::function object that can hold any type of callables (functions, functors, lambdas etc.).
In other wordsstd::bind
creates something that holds information about both object and a function pointer, so you don't need anything extra to call it. -
@Chris-Kawa yeah, I'd actually gotten something working, but I like yours better, because it accepts the callback as an argument, rather than hardcoding it in the timer.
I think this topic is closed, but I wonder if you could give me an explanation for the use of the std::bind. The list is just a list of QObjects; how does this "attach" the callback function?
Thanks!
@mzimmers said in creating hash (or list) of member functions:
but I wonder if you could give me an explanation for the use of the std::bind. The list is just a list of QObjects; how does this "attach" the callback function?
All of this in place of the
typedef void (*clientSlot)()
, with C life used to be so simple :) We can't use that to call a C++ class member function on an instance. So...std::function<void()>
I can be used to call a C++ class member method.
registerClient(T* client, void(T::*cs)())
Here's my client object (of a certain type), and here is the class member function.
clients.push_back(std::bind(cs, client));
Creates and pushes an object which, when invoked, will call
cs(client)
. Which turns out to be the same asclient->cs()
. Which I am just about to question @Chris-Kawa on...! -
@mzimmers In my example it's not a list of QObjects. It's a list of std::function objects.
Clock::update()
is a class member function, so to call it you need an instance of that class i.e.instance->update()
. The way class member functions work is that they really are just regular functions that have a hidden implicitthis
parameter, so in effect it's likeClock::update(instance)
.
std::bind
, as the name suggests, creates a callable object that binds a functor with a parameter, so you can call it as if there was no parameter.
The way to think about it is thatstd::bind(cs, client)
creates a struct with the operator(), something like this:struct Something { Clock* client; void operator()() { client->update(); } }
so it turns a class member function with hidden
this
parameter into something that can be called without parameters. Then I just store it in a std::function object that can hold any type of callables (functions, functors, lambdas etc.).
In other wordsstd::bind
creates something that holds information about both object and a function pointer, so you don't need anything extra to call it.@Chris-Kawa said in creating hash (or list) of member functions:
The way class member functions work is that they really are just regular functions that have a hidden implicit
this
parameter, so in effect it's like Clock::update(instance).OMG! But where does C++ tell you this and that you can write code to use it? I had no idea this was "documented" or "supported". I assumed implementation was opaque/abstract.
-
@Chris-Kawa said in creating hash (or list) of member functions:
The way class member functions work is that they really are just regular functions that have a hidden implicit
this
parameter, so in effect it's like Clock::update(instance).OMG! But where does C++ tell you this and that you can write code to use it? I had no idea this was "documented" or "supported". I assumed implementation was opaque/abstract.
@JonB said:
But where does C++ tell you this and that you can write code to use it?
Well no, you can't currently write it like that. I meant it conceptually. That's just what the compiler does anyway (you can see it e.g. in the mangled function signatures when inspecting C++ library exports).
Although the so called Uniform Call Syntax has been proposed multiple times over the years, including by Mr. C++ himself: N4474, so you might see it in some future standard version.
-
@JonB said:
But where does C++ tell you this and that you can write code to use it?
Well no, you can't currently write it like that. I meant it conceptually. That's just what the compiler does anyway (you can see it e.g. in the mangled function signatures when inspecting C++ library exports).
Although the so called Uniform Call Syntax has been proposed multiple times over the years, including by Mr. C++ himself: N4474, so you might see it in some future standard version.
@Chris-Kawa said in creating hash (or list) of member functions:
Well no, you can't currently write it like that. I meant it conceptually.
Oh, right! For a while there I thought you were saying literally.
I suppose I ought go look at what magic
std::bind()
actually does, then it would be clear. But I just know it's going to look complicated.... :( -
@Chris-Kawa said in creating hash (or list) of member functions:
Well no, you can't currently write it like that. I meant it conceptually.
Oh, right! For a while there I thought you were saying literally.
I suppose I ought go look at what magic
std::bind()
actually does, then it would be clear. But I just know it's going to look complicated.... :(@JonB said:
But I just know it's going to look complicated.... :(
It does look a bit complicated, but it has to deal with variable number of perfectly forwarded arguments and a lot of weird corner cases users come up with. Also it's the standard library, so it's mangled with all those underscore names and defensive programming style, but if you squint a little you'll see it basically returns a class with operator() like I mentioned.
-
@JonB said:
But I just know it's going to look complicated.... :(
It does look a bit complicated, but it has to deal with variable number of perfectly forwarded arguments and a lot of weird corner cases users come up with. Also it's the standard library, so it's mangled with all those underscore names and defensive programming style, but if you squint a little you'll see it basically returns a class with operator() like I mentioned.
@Chris-Kawa
Thanks. You gotta love hardcore C++, it's so... simple and clean. -
@Chris-Kawa
Thanks. You gotta love hardcore C++, it's so... simple and clean.@JonB said in creating hash (or list) of member functions:
@Chris-Kawa
Thanks. You gotta love hardcore C++, it's so... simple and clean.Now, now...no sarcasm.
But yeah...wouldn't you love to have today's compute resources available for solving the problems of 30 years ago?
-
@mzimmers said in creating hash (or list) of member functions:
but I wonder if you could give me an explanation for the use of the std::bind. The list is just a list of QObjects; how does this "attach" the callback function?
All of this in place of the
typedef void (*clientSlot)()
, with C life used to be so simple :) We can't use that to call a C++ class member function on an instance. So...std::function<void()>
I can be used to call a C++ class member method.
registerClient(T* client, void(T::*cs)())
Here's my client object (of a certain type), and here is the class member function.
clients.push_back(std::bind(cs, client));
Creates and pushes an object which, when invoked, will call
cs(client)
. Which turns out to be the same asclient->cs()
. Which I am just about to question @Chris-Kawa on...!@JonB said in creating hash (or list) of member functions:
All of this in place of the typedef void (*clientSlot)(), with C life used to be so simple :) We can't use that to call a C++ class member function on an instance
who says you can't ?
#include <array> class SomeClass : public QObject { Q_OBJECT typedef void (SomeClass::*SomeClassFunction)(); std::array<SomeClassFunction,3> arrayOfSignalsPointers{&SomeClass::signal1,&SomeClass::signal2, &SomeClass::signal3}; std::array<SomeClassFunction, 3> arrayOfSlotsPointers{&SomeClass::slot1, &SomeClass::slot2, &SomeClass::slot3}; public: explicit SomeClass(QObject *parent = nullptr) : QObject(parent) { QObject::connect(this, &SomeClass::signal1, this, &SomeClass::slot1); QObject::connect(this, &SomeClass::signal2, this, &SomeClass::slot2); QObject::connect(this, &SomeClass::signal3, this, &SomeClass::slot3); qDebug() << "Emit all signals"; for(auto entry : arrayOfSignalsPointers){ (this->*entry)(); } qDebug() << "Call all slots directly"; for(auto entry : arrayOfSlotsPointers){ (this->*entry)(); } } signals: void signal1(); void signal2(); void signal3(); public slots: void slot1(){qDebug() << Q_FUNC_INFO;} void slot2(){qDebug() << Q_FUNC_INFO;} void slot3(){qDebug() << Q_FUNC_INFO;} };
-
@JonB said in creating hash (or list) of member functions:
All of this in place of the typedef void (*clientSlot)(), with C life used to be so simple :) We can't use that to call a C++ class member function on an instance
who says you can't ?
#include <array> class SomeClass : public QObject { Q_OBJECT typedef void (SomeClass::*SomeClassFunction)(); std::array<SomeClassFunction,3> arrayOfSignalsPointers{&SomeClass::signal1,&SomeClass::signal2, &SomeClass::signal3}; std::array<SomeClassFunction, 3> arrayOfSlotsPointers{&SomeClass::slot1, &SomeClass::slot2, &SomeClass::slot3}; public: explicit SomeClass(QObject *parent = nullptr) : QObject(parent) { QObject::connect(this, &SomeClass::signal1, this, &SomeClass::slot1); QObject::connect(this, &SomeClass::signal2, this, &SomeClass::slot2); QObject::connect(this, &SomeClass::signal3, this, &SomeClass::slot3); qDebug() << "Emit all signals"; for(auto entry : arrayOfSignalsPointers){ (this->*entry)(); } qDebug() << "Call all slots directly"; for(auto entry : arrayOfSlotsPointers){ (this->*entry)(); } } signals: void signal1(); void signal2(); void signal3(); public slots: void slot1(){qDebug() << Q_FUNC_INFO;} void slot2(){qDebug() << Q_FUNC_INFO;} void slot3(){qDebug() << Q_FUNC_INFO;} };
@J-Hilk said in creating hash (or list) of member functions:
typedef void (SomeClass::*SomeClassFunction)();
I said that you cannot use
typedef void (*clientSlot)();
, as the OP wrote and one would in C, to call a C++ member function. And you can't: as you show you needClassName::*function
not just plain*function
.Having said that, I was nonetheless not aware that you can use that to get a member function's address and then call
(instance->*memberFunctionPointer)()
. Thank you for clarifying.So.... all this
std::function<>
and particularlystd::bind()
looks like the usual C++ "why would you want to write something simple when you can wrap it up to be complicated"? ;-) -
@J-Hilk said in creating hash (or list) of member functions:
typedef void (SomeClass::*SomeClassFunction)();
I said that you cannot use
typedef void (*clientSlot)();
, as the OP wrote and one would in C, to call a C++ member function. And you can't: as you show you needClassName::*function
not just plain*function
.Having said that, I was nonetheless not aware that you can use that to get a member function's address and then call
(instance->*memberFunctionPointer)()
. Thank you for clarifying.So.... all this
std::function<>
and particularlystd::bind()
looks like the usual C++ "why would you want to write something simple when you can wrap it up to be complicated"? ;-)@JonB said:
So.... all this std::function<> and particularly std::bind() looks like the usual C++ "why would you want to write something simple when you can wrap it up to be complicated"? ;-)
No, it's a way to be generic. To write
typedef void (SomeClass::*SomeClassFunction)()
you have to hardcodeSomeClass
i.e. know it up front. Notice that what @J-Hilk posted will work with one particular class only. I know he just shows how to call a member function from a pointer and that's fine, but it doesn't do much for the original problem.
std::function doesn't care. It just takes any functor you give it and std::bind creates a functor from anything callable you give it. -
@JonB said:
So.... all this std::function<> and particularly std::bind() looks like the usual C++ "why would you want to write something simple when you can wrap it up to be complicated"? ;-)
No, it's a way to be generic. To write
typedef void (SomeClass::*SomeClassFunction)()
you have to hardcodeSomeClass
i.e. know it up front. Notice that what @J-Hilk posted will work with one particular class only. I know he just shows how to call a member function from a pointer and that's fine, but it doesn't do much for the original problem.
std::function doesn't care. It just takes any functor you give it and std::bind creates a functor from anything callable you give it.@Chris-Kawa
Ah yes, I get it.Modern C++ programming is hugely about templates. But, correct me if I am wrong, C++ did not start out with templates, did it?
-
@Chris-Kawa
Ah yes, I get it.Modern C++ programming is hugely about templates. But, correct me if I am wrong, C++ did not start out with templates, did it?
-
@J-Hilk Where "initial commit" at that time would probably be Stroustrup saving it to a big floppy and physically carrying it to the cubicle of his coworkers. Good old times :D
-
@J-Hilk Where "initial commit" at that time would probably be Stroustrup saving it to a big floppy and physically carrying it to the cubicle of his coworkers. Good old times :D
We were beyond floppies by then. The first computer I used had 10.5 inch (I think, unless it was only 8 inch) floppies, https://www.computinghistory.org.uk/det/10247/Nord-ND305-355-Floppy-Disk/ :)