2
votes

I have a base class, and it have a member function that sometime will be called. Usually, this function have a parameter that pointing to itself.

class Base {

public:
    std::function<bool(Base *, int)> foo;

private:
    int x{};

public:
    static std::shared_ptr<Base> create() {
        return std::make_shared<Base>();
    }

    Base() = default;

    const std::function<bool(Base *, int)> &getFoo() const {
        return foo;
    }

    void setFoo(const std::function<bool(Base *, int)> &foo) {
        Base::foo = foo;
    }

    int getX() const {
        return x;
    }

    void setX(int x) {
        Base::x = x;
    }
};

But when I have a derived class, how can I set this member function? Although the base class pointer can point to a subclass object, but I directly passed into the derived object, the compiler does not pass.

class Derived : public Base {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }
};

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Derived *derived, int x) -> bool { return derived->getX() > x; });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

error: no viable conversion from '(lambda at main.cpp:62:15)' to 'const std::function' b->setFoo([](Derived *derived, int x) -> bool { return derived->getX() > x; }); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

So, is there any good idea to pass a closure to base class, and base class call it instead of derived class, and the most important thing is that closure have a parameter which is point to who pass the closure!

3
Did you try to change the lambda definition to use Base?Robert Andrzejuk

3 Answers

6
votes

Note

I am going to assume that for some reason the closure in question needs access to Derived's methods/data members, and the OP's example does not convey that very well. Otherwise, why not just use Base * as the input parameter:

b->setFoo([](Base *derived, int x) -> bool { return derived->getX() > x; });

@user3655463's answer contains the full code for this case.

Simple solution

In case the CRTP solution proposed by @Yuki does not work for you, you could just use Base * as an argument of the closure and static_cast it in the closure body (the compiler can optimize away the cast), like this:

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Base *derived, int x) -> bool {
        return static_cast<Derived *>(derived)->getX() > x;
    });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

Live example.

If you really need the type in the closure to be Derived *

In case having Base * in the closure is not acceptable, you could hide the setFoo method from Base with a special implementation in Derived which will do the cast for you:

class Derived : public Base {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }

    template <typename Closure>
    void setFoo(Closure foo) {
        Base::setFoo([foo](Base *base, int x) {
            return foo(static_cast<Derived *>(base), x);
        });
    }
};


int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Derived *derived, int x) -> bool {
        return derived->getX() > x;
    });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

This allows you to use the same interface as you have in your original main funciton.

Live example.

If you have a lot of derived classes, and don't want to hide that method over and over again in each class

Now things get a bit complicated, and note that it's a good chance doing something like this would be overengineering in your case, but I just want to demonstrate that it can be done - here is where CRTP comes into play. It is used to implement a mixin which provides an implementation of the setFoo method:

template <typename ConcreteDerived, typename DirectBase>
class EnableSetFooAndInherit : public DirectBase {
public:
    template <typename Closure>
    void setFoo(Closure foo) {
        DirectBase::setFoo([foo](DirectBase *base, int x) {
            return foo(static_cast<ConcreteDerived *>(base), x);
        });
    }
};

class Derived : public EnableSetFooAndInherit<Derived, Base> {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }
};

class Derived2 : public EnableSetFooAndInherit<Derived2, Base> {
public:
    static std::shared_ptr<Derived2> create() {
        return std::make_shared<Derived2>();
    }
};

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Derived *derived, int x) -> bool {
        return derived->getX() > x;
    });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    auto d2 = Derived2::create();
    d2->setX(77);
    d2->setFoo([](Derived2 *derived, int x) -> bool {
        return derived->getX() < x;
    });

    if (d2->getFoo()) {
        auto res = d2->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}

Live example.

2
votes

If a template base solution fits your style then this might work.

template <typename D>
class Base {

public:
  std::function<bool(D*, int)> foo;

private:
  int x{};

public:
  static std::shared_ptr<Base> create() { return std::make_shared<Base>(); }

  Base() = default;

  const std::function<bool(D*, int)>& getFoo() const { return foo; }

  void setFoo(const std::function<bool(D*, int)>& foo) { Base::foo = foo; }

  int getX() const { return x; }

  void setX(int x) { Base::x = x; }
};

class Derived : public Base<Derived> {
public:
  static std::shared_ptr<Derived> create() { return std::make_shared<Derived>(); }
};

int main() {
  auto d = Derived::create();
  d->setX(77);
  d->setFoo([](Derived* derived, int x) -> bool { return derived->getX() > x; });

  if (d->getFoo()) {
    auto res = d->foo(d.get(), 99);
    std::cout << res << std::endl;
  }

  return 0;
}
2
votes

Can't you just use Base (just as you designed):

d->setFoo([](Base* derived, int x) -> bool { return derived->getX() > x; });

Whole code:

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>
#include <memory>

class Base {

public:
    std::function<bool(Base *, int)> foo;

private:
    int x{};

public:
    static std::shared_ptr<Base> create() {
        return std::make_shared<Base>();
    }

    Base() = default;

    const std::function<bool(Base *, int)> &getFoo() const {
        return foo;
    }

    void setFoo(const std::function<bool(Base *, int)> &foo) {
        Base::foo = foo;
    }

    int getX() const {
        return x;
    }

    void setX(int x) {
        Base::x = x;
    }

};

class Derived : public Base {
public:
    static std::shared_ptr<Derived> create() {
        return std::make_shared<Derived>();
    }
};

int main() {
    auto d = Derived::create();
    d->setX(77);
    d->setFoo([](Base* derived, int x) -> bool { return derived->getX() > x; });

    if (d->getFoo()) {
        auto res = d->foo(d.get(), 99);
        std::cout << res << std::endl;
    }

    return 0;
}