Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Special Interest Groups
  3. C++ Gurus
  4. template function with signal as parameter
Forum Updated to NodeBB v4.3 + New Features

template function with signal as parameter

Scheduled Pinned Locked Moved Solved C++ Gurus
19 Posts 5 Posters 2.6k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • mzimmersM mzimmers

    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...

    Christian EhrlicherC Offline
    Christian EhrlicherC Offline
    Christian Ehrlicher
    Lifetime Qt Champion
    wrote on last edited by
    #2

    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 lambda

    void emitMySignal(const std::function<void(int)> &signalFunc)
    {
      emit signalFunc(42);
    }
    
    void foo::doSomething()
    {
      emitMySignal([this](int val) { emit myFooSignal(val); });
    }
    

    Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
    Visit the Qt Academy at https://academy.qt.io/catalog

    mzimmersM 1 Reply Last reply
    1
    • Christian EhrlicherC Christian Ehrlicher

      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 lambda

      void emitMySignal(const std::function<void(int)> &signalFunc)
      {
        emit signalFunc(42);
      }
      
      void foo::doSomething()
      {
        emitMySignal([this](int val) { emit myFooSignal(val); });
      }
      
      mzimmersM Offline
      mzimmersM Offline
      mzimmers
      wrote on last edited by
      #3

      @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 EhrlicherC 1 Reply Last reply
      0
      • mzimmersM mzimmers

        @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 EhrlicherC Offline
        Christian EhrlicherC Offline
        Christian Ehrlicher
        Lifetime Qt Champion
        wrote on last edited by
        #4

        @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

        Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
        Visit the Qt Academy at https://academy.qt.io/catalog

        mzimmersM 1 Reply Last reply
        0
        • Christian EhrlicherC Christian Ehrlicher

          @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

          mzimmersM Offline
          mzimmersM Offline
          mzimmers
          wrote on last edited by
          #5

          @Christian-Ehrlicher I'm trying to code the signal as a variable, so that various callers can specify their signal.

          Christian EhrlicherC 1 Reply Last reply
          0
          • mzimmersM mzimmers

            @Christian-Ehrlicher I'm trying to code the signal as a variable, so that various callers can specify their signal.

            Christian EhrlicherC Offline
            Christian EhrlicherC Offline
            Christian Ehrlicher
            Lifetime Qt Champion
            wrote on last edited by Christian Ehrlicher
            #6

            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.

            Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
            Visit the Qt Academy at https://academy.qt.io/catalog

            mzimmersM 1 Reply Last reply
            0
            • Christian EhrlicherC Christian Ehrlicher

              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.

              mzimmersM Offline
              mzimmersM Offline
              mzimmers
              wrote on last edited by mzimmers
              #7

              @Christian-Ehrlicher

              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.

              JonBJ 1 Reply Last reply
              0
              • mzimmersM mzimmers

                @Christian-Ehrlicher

                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.

                JonBJ Offline
                JonBJ Offline
                JonB
                wrote on last edited by JonB
                #8

                @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 a Metrics, if called not from that class will need to be a suitable instance.] Or am I quite off-base?!

                Christian EhrlicherC 1 Reply Last reply
                3
                • JonBJ 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 a Metrics, if called not from that class will need to be a suitable instance.] Or am I quite off-base?!

                  Christian EhrlicherC Offline
                  Christian EhrlicherC Offline
                  Christian Ehrlicher
                  Lifetime Qt Champion
                  wrote on last edited by
                  #9

                  @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...

                  Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                  Visit the Qt Academy at https://academy.qt.io/catalog

                  Christian EhrlicherC J.HilkJ 2 Replies Last reply
                  0
                  • Christian EhrlicherC Christian Ehrlicher

                    @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 EhrlicherC Offline
                    Christian EhrlicherC Offline
                    Christian Ehrlicher
                    Lifetime Qt Champion
                    wrote on last edited by Christian Ehrlicher
                    #10

                    And 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);
                    }
                    

                    Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                    Visit the Qt Academy at https://academy.qt.io/catalog

                    1 Reply Last reply
                    3
                    • Christian EhrlicherC Christian Ehrlicher

                      @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...

                      J.HilkJ Offline
                      J.HilkJ Offline
                      J.Hilk
                      Moderators
                      wrote on last edited by
                      #11

                      @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


                      Be aware of the Qt Code of Conduct, when posting : https://forum.qt.io/topic/113070/qt-code-of-conduct


                      Q: What's that?
                      A: It's blue light.
                      Q: What does it do?
                      A: It turns blue.

                      mzimmersM 1 Reply Last reply
                      0
                      • J.HilkJ J.Hilk

                        @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

                        mzimmersM Offline
                        mzimmersM Offline
                        mzimmers
                        wrote on last edited by
                        #12

                        @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.

                        mzimmersM 1 Reply Last reply
                        0
                        • mzimmersM mzimmers

                          @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.

                          mzimmersM Offline
                          mzimmersM Offline
                          mzimmers
                          wrote on last edited by mzimmers
                          #13

                          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...

                          Christian EhrlicherC 1 Reply Last reply
                          0
                          • mzimmersM mzimmers

                            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...

                            Christian EhrlicherC Offline
                            Christian EhrlicherC Offline
                            Christian Ehrlicher
                            Lifetime Qt Champion
                            wrote on last edited by
                            #14

                            @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.

                            Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                            Visit the Qt Academy at https://academy.qt.io/catalog

                            JonBJ mzimmersM 2 Replies Last reply
                            1
                            • Christian EhrlicherC Christian Ehrlicher

                              @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.

                              JonBJ Offline
                              JonBJ Offline
                              JonB
                              wrote on last edited by JonB
                              #15

                              @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 same value as the first parameter. Let's say we wish to impose that value will only be passed as first parameter and a signalfunc will be passed as just a function reference and will be called via signalfunc(value) by compareNotify()'s body code.

                              Now, you said earlier this a problem when signalfunc is a class member function. When you do Qt's connect() 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?

                              1 Reply Last reply
                              0
                              • Christian EhrlicherC Christian Ehrlicher

                                @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.

                                mzimmersM Offline
                                mzimmersM Offline
                                mzimmers
                                wrote on last edited by
                                #16

                                @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 EhrlicherC 1 Reply Last reply
                                0
                                • mzimmersM mzimmers

                                  @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 EhrlicherC Offline
                                  Christian EhrlicherC Offline
                                  Christian Ehrlicher
                                  Lifetime Qt Champion
                                  wrote on last edited by
                                  #17

                                  I gave another hint how to use it without std::function but it seems reading is hard nowadays.

                                  Qt Online Installer direct download: https://download.qt.io/official_releases/online_installers/
                                  Visit the Qt Academy at https://academy.qt.io/catalog

                                  1 Reply Last reply
                                  0
                                  • mzimmersM mzimmers has marked this topic as solved on
                                  • S Offline
                                    S Offline
                                    SimonSchroeder
                                    wrote on last edited by
                                    #18

                                    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 use this within compareNotify 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 a T as parameter.

                                    mzimmersM 1 Reply Last reply
                                    4
                                    • S SimonSchroeder

                                      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 use this within compareNotify 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 a T as parameter.

                                      mzimmersM Offline
                                      mzimmersM Offline
                                      mzimmers
                                      wrote on last edited by mzimmers
                                      #19

                                      @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 Reply Last reply
                                      1

                                      • Login

                                      • Login or register to search.
                                      • First post
                                        Last post
                                      0
                                      • Categories
                                      • Recent
                                      • Tags
                                      • Popular
                                      • Users
                                      • Groups
                                      • Search
                                      • Get Qt Extensions
                                      • Unsolved