Message ID | 20220830030615.29089-1-laurent.pinchart@ideasonboard.com |
---|---|
State | Accepted |
Headers | show |
Series |
|
Related | show |
Hi Laurent, On Tue, Aug 30, 2022 at 06:06:15AM +0300, Laurent Pinchart wrote: > If a pointer-to-member is passed to the Signal::connect() function with > arguments that don't match the Signal type, the pointer-to-member > version of connect() will not match during template argument resolution, > but the functor version will. This results in a compilation error in the > BoundMethodFunctor class, due to the pointer-to-member not being a > functor and thus not being callable directly. The error messages are > quite cryptic. With the following error applied, > > diff --git a/test/signal.cpp b/test/signal.cpp > index 5c6b304dac0b..6dd11ac45313 100644 > --- a/test/signal.cpp > +++ b/test/signal.cpp > @@ -107,6 +107,7 @@ protected: > /* Test signal emission and reception. */ > called_ = false; > signalVoid_.connect(this, &SignalTest::slotVoid); > + signalVoid_.connect(this, &SignalTest::slotInteger1); > signalVoid_.emit(); > > if (!called_) { > > gcc outputs > > ../../include/libcamera/base/bound_method.h: In instantiation of ‘R libcamera::BoundMethodFunctor<T, R, Func, Args>::activate(Args ..., bool) [with T = SignalTest; R = void; Func = void (SignalTest::*)(int); Args = {}]’: > ../../include/libcamera/base/bound_method.h:143:4: required from here > ../../include/libcamera/base/bound_method.h:146:37: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’, e.g. ‘(... ->* ((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTes > t::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_) (...)’ > 146 | return func_(args...); > | ~~~~~^~~~~~~~~ > > and clang isn't much better: > > ../../include/libcamera/base/bound_method.h:146:11: error: called object type 'void (SignalTest::*)(int)' is not a function or function pointer > return func_(args...); > ^~~~~ > ../../include/libcamera/base/bound_method.h:137:2: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::activate' requested here > BoundMethodFunctor(T *obj, Object *object, Func func, > ^ > ../../include/libcamera/base/signal.h:80:27: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::BoundMethodFunctor' requested here > SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); > ^ > ../../test/signal.cpp:110:15: note: in instantiation of function template specialization 'libcamera::Signal<>::connect<SignalTest, void (SignalTest::*)(int), nullptr>' requested here > signalVoid_.connect(this, &SignalTest::slotInteger1); > ^ > > Improve error reporting by disabling the functor version of connect() > when the Func argument isn't invocable with the Signal arguments. gcc > will then complain with > > ../../test/signal.cpp:110:36: error: no matching function for call to ‘libcamera::Signal<>::connect(SignalTest*, void (SignalTest::*)(int))’ > 110 | signalVoid_.connect(this, &SignalTest::slotInteger1); > | ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > and clang with > > ../../test/signal.cpp:110:15: error: no matching member function for call to 'connect' > signalVoid_.connect(this, &SignalTest::slotInteger1); > ~~~~~~~~~~~~^~~~~~~ > > which are more readable. > > This change requires usage of std::is_invocable<>, which is only > available starting in C++17. This is fine for usage of the Signal class > within libcamera, as the project is compiled with C++17, but we try to > keep the public API compatible C++14. Condition the additional checks > based on the C++ version. > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> \o/ Reviewed-by: Paul Elder <paul.elder@ideasonboard.com> > --- > include/libcamera/base/signal.h | 12 ++++++++++-- > 1 file changed, 10 insertions(+), 2 deletions(-) > > diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h > index efb591bc5073..841e4b4ca15c 100644 > --- a/include/libcamera/base/signal.h > +++ b/include/libcamera/base/signal.h > @@ -63,7 +63,11 @@ public: > > #ifndef __DOXYGEN__ > template<typename T, typename Func, > - std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> > + std::enable_if_t<std::is_base_of<Object, T>::value > +#if __cplusplus >= 201703L > + && std::is_invocable_v<Func, Args...> > +#endif > + > * = nullptr> > void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) > { > Object *object = static_cast<Object *>(obj); > @@ -71,7 +75,11 @@ public: > } > > template<typename T, typename Func, > - std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> > + std::enable_if_t<!std::is_base_of<Object, T>::value > +#if __cplusplus >= 201703L > + && std::is_invocable_v<Func, Args...> > +#endif > + > * = nullptr> > #else > template<typename T, typename Func> > #endif
Quoting Laurent Pinchart via libcamera-devel (2022-08-30 04:06:15) > If a pointer-to-member is passed to the Signal::connect() function with > arguments that don't match the Signal type, the pointer-to-member > version of connect() will not match during template argument resolution, > but the functor version will. This results in a compilation error in the > BoundMethodFunctor class, due to the pointer-to-member not being a > functor and thus not being callable directly. The error messages are > quite cryptic. With the following error applied, > > diff --git a/test/signal.cpp b/test/signal.cpp > index 5c6b304dac0b..6dd11ac45313 100644 > --- a/test/signal.cpp > +++ b/test/signal.cpp > @@ -107,6 +107,7 @@ protected: > /* Test signal emission and reception. */ > called_ = false; > signalVoid_.connect(this, &SignalTest::slotVoid); > + signalVoid_.connect(this, &SignalTest::slotInteger1); > signalVoid_.emit(); > > if (!called_) { > > gcc outputs > > ../../include/libcamera/base/bound_method.h: In instantiation of ‘R libcamera::BoundMethodFunctor<T, R, Func, Args>::activate(Args ..., bool) [with T = SignalTest; R = void; Func = void (SignalTest::*)(int); Args = {}]’: > ../../include/libcamera/base/bound_method.h:143:4: required from here > ../../include/libcamera/base/bound_method.h:146:37: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’, e.g. ‘(... ->* ((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTes > t::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_) (...)’ > 146 | return func_(args...); > | ~~~~~^~~~~~~~~ > > and clang isn't much better: > > ../../include/libcamera/base/bound_method.h:146:11: error: called object type 'void (SignalTest::*)(int)' is not a function or function pointer > return func_(args...); > ^~~~~ > ../../include/libcamera/base/bound_method.h:137:2: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::activate' requested here > BoundMethodFunctor(T *obj, Object *object, Func func, > ^ > ../../include/libcamera/base/signal.h:80:27: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::BoundMethodFunctor' requested here > SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); > ^ > ../../test/signal.cpp:110:15: note: in instantiation of function template specialization 'libcamera::Signal<>::connect<SignalTest, void (SignalTest::*)(int), nullptr>' requested here > signalVoid_.connect(this, &SignalTest::slotInteger1); > ^ > > Improve error reporting by disabling the functor version of connect() > when the Func argument isn't invocable with the Signal arguments. gcc > will then complain with > > ../../test/signal.cpp:110:36: error: no matching function for call to ‘libcamera::Signal<>::connect(SignalTest*, void (SignalTest::*)(int))’ > 110 | signalVoid_.connect(this, &SignalTest::slotInteger1); > | ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > and clang with > > ../../test/signal.cpp:110:15: error: no matching member function for call to 'connect' > signalVoid_.connect(this, &SignalTest::slotInteger1); > ~~~~~~~~~~~~^~~~~~~ > > which are more readable. > > This change requires usage of std::is_invocable<>, which is only > available starting in C++17. This is fine for usage of the Signal class > within libcamera, as the project is compiled with C++17, but we try to > keep the public API compatible C++14. Condition the additional checks > based on the C++ version. > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> I can't really say I have 'reviewed' this, however I like what it promises, and the change itself looks small and straightforward. So because of unknown template magic, (that I could go read up on, but I'm not going to right now), I'll just add this. I think it should go in. Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > --- > include/libcamera/base/signal.h | 12 ++++++++++-- > 1 file changed, 10 insertions(+), 2 deletions(-) > > diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h > index efb591bc5073..841e4b4ca15c 100644 > --- a/include/libcamera/base/signal.h > +++ b/include/libcamera/base/signal.h > @@ -63,7 +63,11 @@ public: > > #ifndef __DOXYGEN__ > template<typename T, typename Func, > - std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> > + std::enable_if_t<std::is_base_of<Object, T>::value > +#if __cplusplus >= 201703L > + && std::is_invocable_v<Func, Args...> > +#endif > + > * = nullptr> > void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) > { > Object *object = static_cast<Object *>(obj); > @@ -71,7 +75,11 @@ public: > } > > template<typename T, typename Func, > - std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> > + std::enable_if_t<!std::is_base_of<Object, T>::value > +#if __cplusplus >= 201703L > + && std::is_invocable_v<Func, Args...> > +#endif > + > * = nullptr> > #else > template<typename T, typename Func> > #endif > -- > Regards, > > Laurent Pinchart >
On Wed, Aug 31, 2022 at 11:17:32AM +0100, Kieran Bingham wrote: > Quoting Laurent Pinchart via libcamera-devel (2022-08-30 04:06:15) > > If a pointer-to-member is passed to the Signal::connect() function with > > arguments that don't match the Signal type, the pointer-to-member > > version of connect() will not match during template argument resolution, > > but the functor version will. This results in a compilation error in the > > BoundMethodFunctor class, due to the pointer-to-member not being a > > functor and thus not being callable directly. The error messages are > > quite cryptic. With the following error applied, > > > > diff --git a/test/signal.cpp b/test/signal.cpp > > index 5c6b304dac0b..6dd11ac45313 100644 > > --- a/test/signal.cpp > > +++ b/test/signal.cpp > > @@ -107,6 +107,7 @@ protected: > > /* Test signal emission and reception. */ > > called_ = false; > > signalVoid_.connect(this, &SignalTest::slotVoid); > > + signalVoid_.connect(this, &SignalTest::slotInteger1); > > signalVoid_.emit(); > > > > if (!called_) { > > > > gcc outputs > > > > ../../include/libcamera/base/bound_method.h: In instantiation of ‘R libcamera::BoundMethodFunctor<T, R, Func, Args>::activate(Args ..., bool) [with T = SignalTest; R = void; Func = void (SignalTest::*)(int); Args = {}]’: > > ../../include/libcamera/base/bound_method.h:143:4: required from here > > ../../include/libcamera/base/bound_method.h:146:37: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’, e.g. ‘(... ->* ((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTes > > t::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_) (...)’ > > 146 | return func_(args...); > > | ~~~~~^~~~~~~~~ > > > > and clang isn't much better: > > > > ../../include/libcamera/base/bound_method.h:146:11: error: called object type 'void (SignalTest::*)(int)' is not a function or function pointer > > return func_(args...); > > ^~~~~ > > ../../include/libcamera/base/bound_method.h:137:2: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::activate' requested here > > BoundMethodFunctor(T *obj, Object *object, Func func, > > ^ > > ../../include/libcamera/base/signal.h:80:27: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::BoundMethodFunctor' requested here > > SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); > > ^ > > ../../test/signal.cpp:110:15: note: in instantiation of function template specialization 'libcamera::Signal<>::connect<SignalTest, void (SignalTest::*)(int), nullptr>' requested here > > signalVoid_.connect(this, &SignalTest::slotInteger1); > > ^ > > > > Improve error reporting by disabling the functor version of connect() > > when the Func argument isn't invocable with the Signal arguments. gcc > > will then complain with > > > > ../../test/signal.cpp:110:36: error: no matching function for call to ‘libcamera::Signal<>::connect(SignalTest*, void (SignalTest::*)(int))’ > > 110 | signalVoid_.connect(this, &SignalTest::slotInteger1); > > | ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ > > > > and clang with > > > > ../../test/signal.cpp:110:15: error: no matching member function for call to 'connect' > > signalVoid_.connect(this, &SignalTest::slotInteger1); > > ~~~~~~~~~~~~^~~~~~~ > > > > which are more readable. > > > > This change requires usage of std::is_invocable<>, which is only > > available starting in C++17. This is fine for usage of the Signal class > > within libcamera, as the project is compiled with C++17, but we try to > > keep the public API compatible C++14. Condition the additional checks > > based on the C++ version. > > > > Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> > > I can't really say I have 'reviewed' this, however I like what it > promises, and the change itself looks small and straightforward. > > So because of unknown template magic, (that I could go read up on, but > I'm not going to right now), I'll just add this. I think it should go > in. :-) I went through the process of debugging this with Paul, the knowledge we gained should be shared. Let me give it a try. The original error message from gcc is ../../include/libcamera/base/bound_method.h: In instantiation of ‘R libcamera::BoundMethodFunctor<T, R, Func, Args>::activate(Args ..., bool) [with T = SignalTest; R = void; Func = void (SignalTest::*)(int); Args = {}]’: ../../include/libcamera/base/bound_method.h:143:4: required from here ../../include/libcamera/base/bound_method.h:146:37: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’, e.g. ‘(... ->* ((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_) (...)’ 146 | return func_(args...); | ~~~~~^~~~~~~~~ Let's focus on the error line first. The issue occurs at ../../include/libcamera/base/bound_method.h:146:37 and the error message is error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in [...] Let's look at the code on line 146: template<typename T, typename R, typename Func, typename... Args> class BoundMethodFunctor : public BoundMethodArgs<R, Args...> { public: [...] R activate(Args... args, bool deleteMethod = false) override { if (!this->object_) -> return func_(args...); auto pack = std::make_shared<PackType>(args...); bool sync = BoundMethodBase::activatePack(pack, deleteMethod); return sync ? pack->returnValue() : R(); } [...] }; The compiler complains that `func_(args...)` is not a valid way to call a pointer-to-member function. That's right, a pointer-to-member needs to be called on a particular object instance, such as in BoundMethodMember::activate(): return (obj->*func_)(args...); But why are we reaching BoundMethodFunctor::activate() in the first place if func_ is a pointer-to-member, as BoundMethodMember should be used in that case ? The answer lies in the rest of the error message: [...] in ‘((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’, e.g. ‘(... ->* ((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_) (...)’ Let's strip the libcamera:: namespace qualifier to make it easier to read: [...] in ‘((BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’ [...] This means [...] in ‘((Type *)this)->Type::func_ (...)’ [...] with Type being BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)> That's already more readable, and indeed, the code tries to call func_(), which is a member variable of the BoundMethodFunctor class. So why does the compiler pick BoundMethodFunctor if func_ is a pointer-to-member ? Let's look at the error message from clang, which brings more information: ../../include/libcamera/base/bound_method.h:146:11: error: called object type 'void (SignalTest::*)(int)' is not a function or function pointer return func_(args...); ^~~~~ ../../include/libcamera/base/bound_method.h:137:2: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::activate' requested here BoundMethodFunctor(T *obj, Object *object, Func func, ^ ../../include/libcamera/base/signal.h:80:27: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::BoundMethodFunctor' requested here SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); ^ ../../test/signal.cpp:110:15: note: in instantiation of function template specialization 'libcamera::Signal<>::connect<SignalTest, void (SignalTest::*)(int), nullptr>' requested here signalVoid_.connect(this, &SignalTest::slotInteger1); We can see that BoundMethodFunctor is used in Signal::connect(), on line 80 of signal.h (that information wasn't provided by gcc). The code reads template<typename T, typename Func, std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> void connect(T *obj, Func func) { SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); } We also have another version of connect, for pointer-to-member: template<typename T, typename R, std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> void connect(T *obj, R (T::*func)(Args...)) { SignalBase::connect(new BoundMethodMember<T, R, Args...>(obj, nullptr, func)); } So why does the compiler pick the first one ? When performing overload resolution (multiple functions with the same name), the compiler will try to match the arguments passed to the function with the arguments expected by each overload. Here we see that both overloads have the same number of arguments, with a different type for the second argument. As the functions are function templates, the compiler will then try to perform template argument resolution. The caller reads signalVoid_.connect(this, &SignalTest::slotInteger1); with "this" being an instance of SignalTest, and slotInteger1 being a member function declared as void slotInteger1(int value); T is thus resolved to SignalTest, that's easy. For the second argument, `R (T::*func)(Args...)`, R is resolved to void, T matches the previous resolution (as slotInteger1 is a member of SignalTest). Note that Args is not a template argument of the function, but of the Signal class itself: template<typename... Args> class Signal : public SignalBase { [...] }; Let's now go back to the error message from clang, and in particular to ../../test/signal.cpp:110:15: note: in instantiation of function template specialization 'libcamera::Signal<>::connect<SignalTest, void (SignalTest::*)(int), nullptr>' requested here This shows that Signal is instantiated as Signal<>, which is right, in test/signal.cpp we have Signal<> signalVoid_; Args... thus resolves to an empty list, but that doesn't match the arguments to slotInteger1 ! The compiler thus fails to perform template argument resolution for the Signal::connect() overload that is meant to handle pointer-to-member arguments, and thus ignores it. What does it then do ? It picks another candidate for the overload resolution: template<typename T, typename Func, std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> void connect(T *obj, Func func) { SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); } There again it tries to perform template argument resolution. T matches SignalTest as before, and Func happily matches (SignalTest::*)(int), a pointer-to-member, even though the code has been written to expect a functor (an object that can be directly called, such as a lambda, a std::function or an instance of any class that has an operator()). The overload is resolved, and the compiler continues by instantiating a BoundMethodFunctor as instructed by the function. That's where compilation fails, as BoundMethodFunctor tries to call the functor, and a pointer-to-member can't be called that way. What does this teach us? - The solution was in the error message :-) Even without clang, gcc indicated ../../include/libcamera/base/bound_method.h: In instantiation of ‘R libcamera::BoundMethodFunctor<T, R, Func, Args>::activate(Args ..., bool) [with T = SignalTest; R = void; Func = void (SignalTest::*)(int); Args = {}]’: which shows a mismatch between the arguments to Func (int) and the Args (empty list {}). Disection error messages is scary, but if you split them across multiple lines and drop full namespace qualifiers, they become more readable. - clang still gives better error messages than gcc, not so much related to the error itself in this case, but there's more information about the callers for error related to template argument resolution. - This could have been avoided by proper usage of std::enable_if<> to ensure that the template arguments match the expected types. > Acked-by: Kieran Bingham <kieran.bingham@ideasonboard.com> > > > --- > > include/libcamera/base/signal.h | 12 ++++++++++-- > > 1 file changed, 10 insertions(+), 2 deletions(-) > > > > diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h > > index efb591bc5073..841e4b4ca15c 100644 > > --- a/include/libcamera/base/signal.h > > +++ b/include/libcamera/base/signal.h > > @@ -63,7 +63,11 @@ public: > > > > #ifndef __DOXYGEN__ > > template<typename T, typename Func, > > - std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> > > + std::enable_if_t<std::is_base_of<Object, T>::value > > +#if __cplusplus >= 201703L > > + && std::is_invocable_v<Func, Args...> > > +#endif > > + > * = nullptr> > > void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) > > { > > Object *object = static_cast<Object *>(obj); > > @@ -71,7 +75,11 @@ public: > > } > > > > template<typename T, typename Func, > > - std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> > > + std::enable_if_t<!std::is_base_of<Object, T>::value > > +#if __cplusplus >= 201703L > > + && std::is_invocable_v<Func, Args...> > > +#endif > > + > * = nullptr> > > #else > > template<typename T, typename Func> > > #endif
diff --git a/test/signal.cpp b/test/signal.cpp index 5c6b304dac0b..6dd11ac45313 100644 --- a/test/signal.cpp +++ b/test/signal.cpp @@ -107,6 +107,7 @@ protected: /* Test signal emission and reception. */ called_ = false; signalVoid_.connect(this, &SignalTest::slotVoid); + signalVoid_.connect(this, &SignalTest::slotInteger1); signalVoid_.emit(); if (!called_) { gcc outputs ../../include/libcamera/base/bound_method.h: In instantiation of ‘R libcamera::BoundMethodFunctor<T, R, Func, Args>::activate(Args ..., bool) [with T = SignalTest; R = void; Func = void (SignalTest::*)(int); Args = {}]’: ../../include/libcamera/base/bound_method.h:143:4: required from here ../../include/libcamera/base/bound_method.h:146:37: error: must use ‘.*’ or ‘->*’ to call pointer-to-member function in ‘((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_ (...)’, e.g. ‘(... ->* ((libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTes t::*)(int)>*)this)->libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::func_) (...)’ 146 | return func_(args...); | ~~~~~^~~~~~~~~ and clang isn't much better: ../../include/libcamera/base/bound_method.h:146:11: error: called object type 'void (SignalTest::*)(int)' is not a function or function pointer return func_(args...); ^~~~~ ../../include/libcamera/base/bound_method.h:137:2: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::activate' requested here BoundMethodFunctor(T *obj, Object *object, Func func, ^ ../../include/libcamera/base/signal.h:80:27: note: in instantiation of member function 'libcamera::BoundMethodFunctor<SignalTest, void, void (SignalTest::*)(int)>::BoundMethodFunctor' requested here SignalBase::connect(new BoundMethodFunctor<T, void, Func, Args...>(obj, nullptr, func)); ^ ../../test/signal.cpp:110:15: note: in instantiation of function template specialization 'libcamera::Signal<>::connect<SignalTest, void (SignalTest::*)(int), nullptr>' requested here signalVoid_.connect(this, &SignalTest::slotInteger1); ^ Improve error reporting by disabling the functor version of connect() when the Func argument isn't invocable with the Signal arguments. gcc will then complain with ../../test/signal.cpp:110:36: error: no matching function for call to ‘libcamera::Signal<>::connect(SignalTest*, void (SignalTest::*)(int))’ 110 | signalVoid_.connect(this, &SignalTest::slotInteger1); | ~~~~~~~~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ and clang with ../../test/signal.cpp:110:15: error: no matching member function for call to 'connect' signalVoid_.connect(this, &SignalTest::slotInteger1); ~~~~~~~~~~~~^~~~~~~ which are more readable. This change requires usage of std::is_invocable<>, which is only available starting in C++17. This is fine for usage of the Signal class within libcamera, as the project is compiled with C++17, but we try to keep the public API compatible C++14. Condition the additional checks based on the C++ version. Signed-off-by: Laurent Pinchart <laurent.pinchart@ideasonboard.com> --- include/libcamera/base/signal.h | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/include/libcamera/base/signal.h b/include/libcamera/base/signal.h index efb591bc5073..841e4b4ca15c 100644 --- a/include/libcamera/base/signal.h +++ b/include/libcamera/base/signal.h @@ -63,7 +63,11 @@ public: #ifndef __DOXYGEN__ template<typename T, typename Func, - std::enable_if_t<std::is_base_of<Object, T>::value> * = nullptr> + std::enable_if_t<std::is_base_of<Object, T>::value +#if __cplusplus >= 201703L + && std::is_invocable_v<Func, Args...> +#endif + > * = nullptr> void connect(T *obj, Func func, ConnectionType type = ConnectionTypeAuto) { Object *object = static_cast<Object *>(obj); @@ -71,7 +75,11 @@ public: } template<typename T, typename Func, - std::enable_if_t<!std::is_base_of<Object, T>::value> * = nullptr> + std::enable_if_t<!std::is_base_of<Object, T>::value +#if __cplusplus >= 201703L + && std::is_invocable_v<Func, Args...> +#endif + > * = nullptr> #else template<typename T, typename Func> #endif