1

I am currently working on a small private project using C++ i came up with the following structure:

#include <iostream>

class A
{
    std::vector<int> vec;

protected:
    virtual bool onAdd(int toAdd) {
        // should the 'adding' be suppressed?
        // do some A specific checks
        std::cout << "A::onAdd()" << std::endl;
        return false; 
    }

public:
    void add(int i) {
        if(!onAdd(i)) {
            // actual logic
            vec.push_back(i);
        }
    }
};

class B : public A
{
protected:
    bool onAdd(int toAdd) override {
        // do some B specific checks
        std::cout << "B::onAdd()" << std::endl;
        return false;
    }
};

In this example onAdd is basically meant to be a callback for add, but in a more polymorphic way.

The actual problem arises when a class C inherits from B and wants to override onAdd too. In this case the implementation in B will get discarded (i.e. not called) when calling C::add. So basically what I would like to achieve is a constructor-like behaviour where I am able to override the same method in different positions in the class hierarchy and all of those getting called.

My question now is: Is there a possibility/design to achieve this? I am sure that it wouldn't be as easy as cascading constructors, though.

Note: Don't focus too much on the add example. The question is about the callback like structure and not if it makes sense with an add.

  • 6
    C could just call `B::onAdd` – litelite Aug 31 '17 at 17:41
  • Yeah that's true but I would like to avoid that if possible. – Sebastian Kaupper Aug 31 '17 at 17:42
  • I would like to take the responsibility of calling `B::onAdd` away from `C` because it would simply be wrong behaviour when then `add` gets called. Maybe I need to add that `B` would be kind of a framework boundary. – Sebastian Kaupper Aug 31 '17 at 17:44
  • @SebastianKaupper Well, there's no way to avoid that. You must know what should be done when overriding. There's no automatism like done with constructor functions. Eventually look into the _Function Template Pattern_ if that could be helpful to solve this. – user0042 Aug 31 '17 at 17:45
  • What do you mean by "constructor-like"? A constructor talking an `int` won't automatically call a parent constructor talking an `int`. – Chris Drew Aug 31 '17 at 17:49
  • Well, yeah that's true... I guess I can't hope to get that kind of behaviour when even constructors are capable of this. @user0042 I hoped that there is some kind of structure which allows that. And thanks for the suggestion. It seems like that's basically what I am doing with my `onAdd` isn't it? – Sebastian Kaupper Aug 31 '17 at 17:55
  • @Sebastian You can read more about that [here](https://sourcemaking.com/design_patterns/template_method). – user0042 Aug 31 '17 at 17:59
  • Looks like XY problem, you just call parent explicitly and you should not avoid that, because other "solutions" are much uglier and they do not help, you just did not think enough when you asked. For example where that returned `bool` goes from parent call? – Slava Aug 31 '17 at 18:12
  • @Slava it may or may not be the core problem but I came across this approach and was curious if it's even possible. I am still a student and although I did a lot of small programs in C++ there are certainly a lot of things about the language which I don't know yet. – Sebastian Kaupper Aug 31 '17 at 18:33
  • @SebastianKaupper explicitly calling parent method is very flexible and useful. You can easily implement `A::onAdd() && B::onAdd() && C::onAdd()` or `C::onAdd() && B::onAdd() && A::onAdd()` or non "short-circuit" version of any of them, and all of them will be simple and readable. With your suggested approach that would be a mess. – Slava Aug 31 '17 at 18:45
  • You say "the problem arises when a class `C` inherits from `B`" but the problem is already present with just `A` and `B` isn't it? `A::onAdd` is not called. – Chris Drew Aug 31 '17 at 19:08
  • Yes, of course you are right. – Sebastian Kaupper Aug 31 '17 at 19:10

4 Answers4

3

I would just call my parents onAdd()

bool C::onAdd(int toAdd) {return my_answer && B::onAdd(toAdd);}

This can be a little confusing if you're expecting other developers to inherit from your base class. But for small private hierarchies it works perfectly.

I sometimes include a using statement to make this more explicit

class C : public B
{
  using parent=B;
  bool onAdd(int toAdd) override {return my_answer && parent::onAdd(toAdd);}
};
John Gordon
  • 2,576
  • 3
  • 24
  • 29
  • Yeah that's the obvious one. As it seems like it's not getting better anyway I'll stand with this, thanks. – Sebastian Kaupper Aug 31 '17 at 18:42
  • I don't think it should be the responsibility of a callback to also call the next callback in the list of callbacks. – Chris Drew Sep 01 '17 at 10:41
  • @ChrisDrew: It can be confusing if you're writing a library that you expect other people to use. Way back in the day when OO was becoming popular, calling your parents virtual method seemed to be normal, correct behavior. Twenty years down the road it hasn't worked out like that, but it's still a legitimate technique. – John Gordon Sep 01 '17 at 11:18
2
struct RunAndDiscard {
  template<class Sig, class...Args>
  void operator()(Sig*const* start, Sig*const* finish, Args&&...args)const{
    if (start==finish) return;
    for (auto* i = start; i != (finish-1); ++i) {
      (*i)(args...);
    }
    (*(finish-1))(std::forward<Args>(args)...);
  }
};
template<class Sig, class Combine=RunAndDiscard>
struct invokers {
  std::vector<Sig*> targets;
  template<class...Args>
  decltype(auto) operator()(Args&&...args)const {
    return Combine{}( targets.data(), targets.data()+targets.size(), std::forward<Args>(args)... );
  }
};

struct AndTogetherResultWithShortCircuit {
  template<class Sig, class...Args>
  bool operator()(Sig*const* start, Sig*const* finish, Args&&...args)const{
    if (start==finish) return true;
    for (auto* i = start; i != (finish-1); ++i) {
      if (!(*i)(args...)) return false;
    }
    return (*(finish-1))(std::forward<Args>(args)...);
  }
};

This creates a per-instance table of things to do onAdd.

Creating a per-class table is harder; you need to chain your table with your parent type's table, which requires per-class boilerplate.

There is no way to get the C++ compiler to write either the per-instance version, or the per-class version, without doing it yourself.

There are C++20 proposals involving reflection and reification, plus the metaclass proposal, which may involve automating writing code like this (on both a per-instance and per-class basis).

Here is a live example of this technique being tested:

struct AndTogetherResultWithShortCircuit {
  template<class Sig, class...Args>
  bool operator()(Sig*const* start, Sig*const* finish, Args&&...args)const{
    if (start==finish) return true;
    for (auto* i = start; i != (finish-1); ++i) {
      if (!(*i)(args...)) return false;
    }
    return (*(finish-1))(std::forward<Args>(args)...);
  }
};
class A {
  std::vector<int> vec;
protected:
  invokers<bool(A*, int), AndTogetherResultWithShortCircuit> onAdd;
public:
  void add(int i) {
    if (!onAdd(this, i)) {
      vec.push_back(i);
    }
  }
};
class B : public A
{
public:
   B() {
     onAdd.targets.push_back([](A* self, int x)->bool{
       // do some B specific checks
       std::cout << "B::onAdd(" << x << ")" << std::endl;
        return x%2;
     });
   }
};
class C : public B
{
public:
   C() {
     onAdd.targets.push_back([](A* self, int x)->bool{
       // do some B specific checks
       std::cout << "C::onAdd(" << x << ")" << std::endl;
       return false;
     });
   }
};

When you want to write your own OO-system, you can in C++, but C++ doesn't write it for you.

Yakk - Adam Nevraumont
  • 262,606
  • 27
  • 330
  • 524
  • 1
    Wow. This is a tour de force. (or tour de pharma if a lot coffee was involved) Either way I found this interesting and useful. – wally Aug 31 '17 at 18:26
  • An interesting solution but I have to agree with @rex. Doesn't solve my problem but can surely be useful in other use cases. – Sebastian Kaupper Aug 31 '17 at 18:37
1

If you want a generic solution perhaps you could use CRTP with variadic templates instead of runtime polymophism.

Taking inspiration from this answer and this answer:

template<class... OnAdders> class A : private OnAdders... {
    std::vector<int> vec;

   template<class OnAdder>
   bool onAdd(int toAdd){
     return static_cast<OnAdder*>(this)->onAdd(toAdd);
   }

   template<typename FirstOnAdder, typename SecondOnAdder, class... RestOnAdders>
   bool onAdd(int toAdd){
     if (onAdd<FirstOnAdder>(toAdd))
        return true;

     return onAdd<SecondOnAdder, RestOnAdders...>(toAdd);
   }

public:
    void add(int i) {      
        if (onAdd<OnAdders...>(i))
          return;       

        // actual logic
        vec.push_back(i);
    }
};

class B {
public:
    bool onAdd(int toAdd) {
        // do some B specific checks
        std::cout << "B::onAdd()" << std::endl;
        return false;
    }
};

Which you could use like:

A<B,C> a;
a.add(42);

Live demo.

Chris Drew
  • 14,926
  • 3
  • 34
  • 54
  • Wow. that is actually a pretty nice solution and also kind of similar to what I had expected initially. Thanks for pointing out CRTP, I haven't heard of that before. It should be easy to adapt that idea to fit my needs, thank you. – Sebastian Kaupper Sep 01 '17 at 11:07
0

The following solution uses std::function to add each callback during each constructor:

#include <iostream>
#include <vector>
#include <functional>

class A
{
    std::vector<int> vec;

protected:
    bool onAdd(int toAdd) 
    {
        // do some A specific checks
        std::cout << "A::onAdd()" << std::endl;
        return true;
    }

    // vector of callback functions. Initialized with A::onAdd() callback as the first entry
    std::vector<std::function<bool(int)>> callbacks{{[this](int toAdd){return onAdd(toAdd); }}};

public:
    void add(int i) 
    {
        for(auto& callback : callbacks) {
            if(!callback(i))
                return;
        }
        // actual logic
        vec.push_back(i);
    }
};

class B : public A
{
public:
    B()
    {
        callbacks.emplace_back([this](int toAdd){return onAdd(toAdd); });
    }
protected:
    bool onAdd(int toAdd)  
    {
        // do some B specific checks
        std::cout << "B::onAdd()" << std::endl;
        return true;
    }
};

class C : public B
{
    public:
    C()
    {
        callbacks.emplace_back([this](int toAdd){return onAdd(toAdd); });
    }
protected:
    bool onAdd(int toAdd)  
    {
        // do some C specific checks
        std::cout << "C::onAdd()" << std::endl;
        // must also call B::onAdd()
        return true;
    }

};

int main()
{
    C c;
    c.add(5);
}

Prints:

A::onAdd()
B::onAdd()
C::onAdd()
user0042
  • 7,917
  • 3
  • 24
  • 39
wally
  • 10,717
  • 5
  • 39
  • 72
  • All of this instead of calling `B::onAdd()` in `C::onAdd()`? LOL – Slava Aug 31 '17 at 18:13
  • 1
    @Slava Some of us learn more by feeling the pain rather than just talking about it. :) Clearly the rule of thumb still stands; if you override then the new function (which knows what it is inheriting from) should take full responsibility for calling the right functions and doing the right thing. – wally Aug 31 '17 at 18:19
  • 1
    Agree, the thing is this would not work the way OP wants, as it needs to return `A::onAdd() && B::onAdd() && C::onAdd()` which very easy to implement traditional way, but to create special callback chain for every case? – Slava Aug 31 '17 at 18:21