template function with signal as parameter
-
wrote on 17 Feb 2023, 22:35 last edited by
Hi all -
Hopefully, what I'm trying to do will be somewhat self-explanatory:
template<class T> void Metrics::compareNotify(T &value, T &newValue, T threshold, void (*signal)) { if (abs(newValue - value) > threshold) { value = newValue; emit signal(value); } }
Compare two values, and if they've changed "enough," emit a signal. I'm trying to use it accordingly:
compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, flowRateChanged);
But the compiler is giving me a few errors. This one I can't figure out:
- Candidate function template not viable: no known conversion from 'void (Metrics::)(double)' to 'void ()' for 4th argument
Can someone please advise me on whether what I'm trying to do is possible, and if so, what I'm doing wrong?
Thanks...
-
Hi all -
Hopefully, what I'm trying to do will be somewhat self-explanatory:
template<class T> void Metrics::compareNotify(T &value, T &newValue, T threshold, void (*signal)) { if (abs(newValue - value) > threshold) { value = newValue; emit signal(value); } }
Compare two values, and if they've changed "enough," emit a signal. I'm trying to use it accordingly:
compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, flowRateChanged);
But the compiler is giving me a few errors. This one I can't figure out:
- Candidate function template not viable: no known conversion from 'void (Metrics::)(double)' to 'void ()' for 4th argument
Can someone please advise me on whether what I'm trying to do is possible, and if so, what I'm doing wrong?
Thanks...
You functions wants a plain function but you're trying to pass a member function, this can't work. Also the signature is wrong since you're emitting signal(value) and not signal() as the signature suggests.
I would use a std::function as signature and pass a lambdavoid emitMySignal(const std::function<void(int)> &signalFunc) { emit signalFunc(42); } void foo::doSomething() { emitMySignal([this](int val) { emit myFooSignal(val); }); }
-
You functions wants a plain function but you're trying to pass a member function, this can't work. Also the signature is wrong since you're emitting signal(value) and not signal() as the signature suggests.
I would use a std::function as signature and pass a lambdavoid emitMySignal(const std::function<void(int)> &signalFunc) { emit signalFunc(42); } void foo::doSomething() { emitMySignal([this](int val) { emit myFooSignal(val); }); }
wrote on 17 Feb 2023, 22:50 last edited by@Christian-Ehrlicher I think I see what you're getting at, but I'm still left with the issue of how to pass the signal name into (using your example) doSomething().
-
@Christian-Ehrlicher I think I see what you're getting at, but I'm still left with the issue of how to pass the signal name into (using your example) doSomething().
@mzimmers said in template function with signal as parameter:
the signal name into
I don't see a need for the signal name in my solution
-
@mzimmers said in template function with signal as parameter:
the signal name into
I don't see a need for the signal name in my solution
wrote on 17 Feb 2023, 22:57 last edited by@Christian-Ehrlicher I'm trying to code the signal as a variable, so that various callers can specify their signal.
-
@Christian-Ehrlicher I'm trying to code the signal as a variable, so that various callers can specify their signal.
Lifetime Qt Championwrote on 17 Feb 2023, 23:00 last edited by Christian EhrlicherI still don't see a problem then - simply pass another lambda with the other signal (but it has to have the same signature in my case, you have to fiddle around with the template parameters in your case)
template<class T> void Metrics::compareNotify(T &value, const T newValue, const T threshold, const std::function<void(typename T)> &signalFunc )
may do the trick.
-
I still don't see a problem then - simply pass another lambda with the other signal (but it has to have the same signature in my case, you have to fiddle around with the template parameters in your case)
template<class T> void Metrics::compareNotify(T &value, const T newValue, const T threshold, const std::function<void(typename T)> &signalFunc )
may do the trick.
wrote on 17 Feb 2023, 23:22 last edited by mzimmersclass Metrics : public QObject { void compareNotify(T &value, const T newValue, const T threshold, const std::function<void (T)> &signalFunc); signals: void flowRateChanged(double new_fr); ... } template<class T> void Metrics::compareNotify(T &value, const T newValue, const T threshold, const std::function<void(T)> &signalFunc ) { ... } ... compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, &Metrics::flowRateChanged);
Gives this error:
- Candidate template ignored: could not match 'std::function<void (T)>' against 'void (Metrics::*)(double)'
I'm not sure how I'd use a lambda to pass in the name of flowRateChanged signal.
-
class Metrics : public QObject { void compareNotify(T &value, const T newValue, const T threshold, const std::function<void (T)> &signalFunc); signals: void flowRateChanged(double new_fr); ... } template<class T> void Metrics::compareNotify(T &value, const T newValue, const T threshold, const std::function<void(T)> &signalFunc ) { ... } ... compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, &Metrics::flowRateChanged);
Gives this error:
- Candidate template ignored: could not match 'std::function<void (T)>' against 'void (Metrics::*)(double)'
I'm not sure how I'd use a lambda to pass in the name of flowRateChanged signal.
wrote on 18 Feb 2023, 08:01 last edited by JonB@mzimmers
I am not at all an expert in this area, but I am interested! Isn't @Christian-Ehrlicher telling you to call with a lambda, something like:compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, [this](double val) { emit this->flowRateChanged(val); };
[The
this
needs to be aMetrics
, if called not from that class will need to be a suitable instance.] Or am I quite off-base?! -
@mzimmers
I am not at all an expert in this area, but I am interested! Isn't @Christian-Ehrlicher telling you to call with a lambda, something like:compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, [this](double val) { emit this->flowRateChanged(val); };
[The
this
needs to be aMetrics
, if called not from that class will need to be a suitable instance.] Or am I quite off-base?!@JonB said in template function with signal as parameter:
telling you to call with a lambda
Yes, since my first post but I'm getting ignored...
-
@JonB said in template function with signal as parameter:
telling you to call with a lambda
Yes, since my first post but I'm getting ignored...
Lifetime Qt Championwrote on 18 Feb 2023, 09:10 last edited by Christian EhrlicherAnd if you want a more QObject::connect() - like approach:
template<class T, class OBJ, class FUNC> void compareNotify(T& value, const T newValue, const T threshold, OBJ *object, FUNC func) { if (std::abs(value - newValue) > threshold) { value = newValue; (object->*func)(value); } } struct Foo { void compareAndSet(double val) { compareNotify(m_value, int(val), 1, this, &Foo::intValueChanged); compareNotify(m_dblValue, double(val), 0.5, this, &Foo::doubleValueChanged); } void intValueChanged(int v) { std::cout << "int changed to " << v << std::endl; } void doubleValueChanged(double v) { std::cout << "double changed to " << v << std::endl; } int m_value = 0; double m_dblValue = 0; }; int main(int argc, char* argv[]) { Foo f; f.compareAndSet(2); f.compareAndSet(2.6); }
-
@JonB said in template function with signal as parameter:
telling you to call with a lambda
Yes, since my first post but I'm getting ignored...
@Christian-Ehrlicher Thats sadly normal, theses days.
As soon as you have more than 1 point of information in your answer, people tend to either focus solely on the first point or the last
-
@Christian-Ehrlicher Thats sadly normal, theses days.
As soon as you have more than 1 point of information in your answer, people tend to either focus solely on the first point or the last
wrote on 18 Feb 2023, 15:04 last edited by@J-Hilk @Christian-Ehrlicher I wasn't ignoring anyone's post. I simply don't know lambdas well to enough to fully grasp @Christian-Ehrlicher 's suggestion.
In looking at @JonB 's annotation, I think the confusion might arise from the misconception that there are multiple instances of the Metrics class, each with its own signal defined. This is not the case; a single object handles all (currently 4) metrics I'm updating.
Later today, when I'm more fully awake, I'll look more closely to see about implementing the original suggestion. I want to understand it and make it work.
Thank you for the help.
-
@J-Hilk @Christian-Ehrlicher I wasn't ignoring anyone's post. I simply don't know lambdas well to enough to fully grasp @Christian-Ehrlicher 's suggestion.
In looking at @JonB 's annotation, I think the confusion might arise from the misconception that there are multiple instances of the Metrics class, each with its own signal defined. This is not the case; a single object handles all (currently 4) metrics I'm updating.
Later today, when I'm more fully awake, I'll look more closely to see about implementing the original suggestion. I want to understand it and make it work.
Thank you for the help.
wrote on 19 Feb 2023, 03:59 last edited by mzimmersSo, the problem evidently is with the template declaration:
// this works. void Metrics::compareNotify(double &value, double newValue, double threshold, const std::function<void(double)> &signalFunc ) {...} // this produces an error: // Candidate template ignored: could not match 'std::function<void (T)>' against '(lambda at // C:/Users/Michael.Zimmers/Qt_projects/nga_demo/metrics.cpp:69:19)' template <class T> void Metrics::compareNotify(T &value, T newValue, T threshold, const std::function<void(T)> &signalFunc ) {...} compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, [this](double val) { emit this->flowRateChanged(val); });
I'm out of my depth here. Is the problem with the function parameter?
Thanks...
-
So, the problem evidently is with the template declaration:
// this works. void Metrics::compareNotify(double &value, double newValue, double threshold, const std::function<void(double)> &signalFunc ) {...} // this produces an error: // Candidate template ignored: could not match 'std::function<void (T)>' against '(lambda at // C:/Users/Michael.Zimmers/Qt_projects/nga_demo/metrics.cpp:69:19)' template <class T> void Metrics::compareNotify(T &value, T newValue, T threshold, const std::function<void(T)> &signalFunc ) {...} compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, [this](double val) { emit this->flowRateChanged(val); });
I'm out of my depth here. Is the problem with the function parameter?
Thanks...
@mzimmers said in template function with signal as parameter:
using callFunc = std::function<void(double)>; ... template <class T> void Metrics::compareNotify(T &value, T newValue, T threshold, const callFunc &signalFunc ) ... callFunc f = [this](double val) { emit this->flowRateChanged(val); compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, f);
A lambda and a std::function<> don't automatically convert to each other
Or use the other approach. -
@mzimmers said in template function with signal as parameter:
using callFunc = std::function<void(double)>; ... template <class T> void Metrics::compareNotify(T &value, T newValue, T threshold, const callFunc &signalFunc ) ... callFunc f = [this](double val) { emit this->flowRateChanged(val); compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, f);
A lambda and a std::function<> don't automatically convert to each other
Or use the other approach.wrote on 19 Feb 2023, 08:00 last edited by JonB@Christian-Ehrlicher
I am interested in the following. Technically by passing a lambda as the function it is up to the caller to pass something which uses the samevalue
as the first parameter. Let's say we wish to impose thatvalue
will only be passed as first parameter and asignalfunc
will be passed as just a function reference and will be called viasignalfunc(value)
bycompareNotify()
's body code.Now, you said earlier this a problem when
signalfunc
is a class member function. When you do Qt'sconnect()
the syntax is:connect(..., slotObject, &SlotClass::method);
How would you write
compareNotify()
to follow this pattern?EDIT Ah, sorry, I see you have already written this above at https://forum.qt.io/topic/143059/template-function-with-signal-as-parameter/10, I did not see, thanks that's great. I have not looked, is
connect()
indeed written as a template? -
@mzimmers said in template function with signal as parameter:
using callFunc = std::function<void(double)>; ... template <class T> void Metrics::compareNotify(T &value, T newValue, T threshold, const callFunc &signalFunc ) ... callFunc f = [this](double val) { emit this->flowRateChanged(val); compareNotify(m_flowRate, newFlowRate, FLOW_RATE_NOTIFY_THRESHOLD, f);
A lambda and a std::function<> don't automatically convert to each other
Or use the other approach.wrote on 19 Feb 2023, 13:56 last edited by@Christian-Ehrlicher thanks for the more detailed explanation.
A lambda and a std::function<> don't automatically convert to each other
Ah, OK. I'm curious -- is there any reason you couldn't use a cast for this conversion (other than it would be hugely ugly)?
Also, the goal of this exercise was to create a universal routine that would accept a variety of argument types (int, double, etc). While we've technically accomplished this, we've sort of postponed the problem with this:
using stdFn = std::function<void(double)>;
Is there a way to "templatize" the using statement as well?
-
@Christian-Ehrlicher thanks for the more detailed explanation.
A lambda and a std::function<> don't automatically convert to each other
Ah, OK. I'm curious -- is there any reason you couldn't use a cast for this conversion (other than it would be hugely ugly)?
Also, the goal of this exercise was to create a universal routine that would accept a variety of argument types (int, double, etc). While we've technically accomplished this, we've sort of postponed the problem with this:
using stdFn = std::function<void(double)>;
Is there a way to "templatize" the using statement as well?
I gave another hint how to use it without std::function but it seems reading is hard nowadays.
-
-
wrote on 20 Feb 2023, 08:31 last edited by
The use of a lambda is actually the easiest (and least error-prone) way to implement this function properly. Modern C++ is now 12 years old and lambdas are really useful. You should really learn that little about modern C++. It is worth it!
What you are missing so far is that if you hand over a member function instead of a freestanding function, you have an implicit
this
parameter. This is why you need to specify the function signature as a member function and you need to provide the object on which the function needs to be called as well.First of all, I personally would advice against std::function in general. It has quite an overhead. Function pointers are a lot more efficient. But, that's just my personal taste. (And there are certain applications where std::function is necessary or the overhead doesn't matter.)
The easiest (and not very general) way without a lambda function is to use something like this:
template<class T> void Metrics::compareNotify(T &value, T &newValue, T threshold, void (Metrics::*signal)(T))
(I'm not entirely sure if I got the syntax right; you might need to google for 'member function pointer')
This only allows a signal from the
Metrics
class itself and assumes that you want to usethis
withincompareNotify
as the object for the signal.You can also just let the compiler handle type deduction for you:
void Metric::compareNotify(T &value, T &newValue, T threshold, FUNC signal)
However, error messages might not be as good (but also might be better!) by using this approach. At least this allows you to easily provide lambda.
@Christian-Ehrlicher already posted the most general approach (does not need to be called on
this
):template<class T, class OBJ, class FUNC> void compareNotify(T& value, const T newValue, const T threshold, OBJ *object, FUNC func)
This, however, can also be combined to be a little bit more specific:
template<class T, class OBJ> void compareNotify(T&value, T&newValue, T threshold, OBJ *object, void (OBJ::*signal)(T))
This restricts that the signal needs to be a member function of the provided
object
and takes aT
as parameter. -
The use of a lambda is actually the easiest (and least error-prone) way to implement this function properly. Modern C++ is now 12 years old and lambdas are really useful. You should really learn that little about modern C++. It is worth it!
What you are missing so far is that if you hand over a member function instead of a freestanding function, you have an implicit
this
parameter. This is why you need to specify the function signature as a member function and you need to provide the object on which the function needs to be called as well.First of all, I personally would advice against std::function in general. It has quite an overhead. Function pointers are a lot more efficient. But, that's just my personal taste. (And there are certain applications where std::function is necessary or the overhead doesn't matter.)
The easiest (and not very general) way without a lambda function is to use something like this:
template<class T> void Metrics::compareNotify(T &value, T &newValue, T threshold, void (Metrics::*signal)(T))
(I'm not entirely sure if I got the syntax right; you might need to google for 'member function pointer')
This only allows a signal from the
Metrics
class itself and assumes that you want to usethis
withincompareNotify
as the object for the signal.You can also just let the compiler handle type deduction for you:
void Metric::compareNotify(T &value, T &newValue, T threshold, FUNC signal)
However, error messages might not be as good (but also might be better!) by using this approach. At least this allows you to easily provide lambda.
@Christian-Ehrlicher already posted the most general approach (does not need to be called on
this
):template<class T, class OBJ, class FUNC> void compareNotify(T& value, const T newValue, const T threshold, OBJ *object, FUNC func)
This, however, can also be combined to be a little bit more specific:
template<class T, class OBJ> void compareNotify(T&value, T&newValue, T threshold, OBJ *object, void (OBJ::*signal)(T))
This restricts that the signal needs to be a member function of the provided
object
and takes aT
as parameter.wrote on 21 Feb 2023, 17:02 last edited by mzimmers@SimonSchroeder thank you for the detailed reply.
I'm not averse to using lambda functions (I wholeheartedly agree with you on the subject of using "new" language features, even if they raise the complexity bar a bit). What I was really hoping for was a way to eliminate the type-specific code like this:
struct Foo { void compareAndSet(double val) { compareNotify(m_value, int(val), 1, this, &Foo::intValueChanged); compareNotify(m_dblValue, double(val), 0.5, this, &Foo::doubleValueChanged);
So that my compareNotify could be truly type-agnostic. Perhaps what I'm looking for just isn't feasible, which is fine. The solution that @Christian-Ehrlicher is more than adequate; I was just exploring whether there was another way to do this.
I am not a computer scientist by education, and most of what I know is self-taught. C always made innate sense to me, and the early principles of C++ were logical enough. Some of the more modern stuff, though, is a little unintuitive to me. I'm just an old dog trying to learn a few new tricks.
EDIT:
another note: given that there are only a handful of types in the language, what I'm looking for might be overkill, and might be sacrificing code clarity for universality -- almost never a good trade.
1/19