Home > OS >  C std::function to take functions with sub class parameter
C std::function to take functions with sub class parameter

Time:12-10

[Update] Reason for this question: There are many existing lambdas defined as [](const ChildType1& child), all in a big registry. We want to register new lambdas like [](const ChildType2& child) in the same registry. If we define the function wrapper using Parent, for the many existing lambdas we need to change them to [](const Parent& someone), and inside downcast from Parent to ChildType1.


If I have a function wrapper as std::function<void(const Parent&)>, is there any way to allow it take a function with Parent subclass as parameter, e.g., [](const Child& child){...}, where Child is a subclass of Parent.

Something below does not compile. Online IDE link.

#include <iostream>
#include <functional>

class Parent {
    public:
        virtual void say() const {
            std::cout<<"I am parent"<<"\n";
        }
};

class Child: public Parent {
    public:
        void say() const {
            std::cout<<"I am child"<<"\n";
        }
};

typedef std::function<void(const Parent&)> Wrapper;

int main() {
    Wrapper func=[](const Child& child){  // of course works if Child->Parent
      child.say();
    };
    
    Child c;
    func(c);
    return 0;
}

CodePudding user response:

Why isn't this allowed ?

This is not allowed by the language because it might lead to inconsistencies.

With your definition of Wrapper, the following code should be legitimate:

Wrapper f; 
Parent x; 
... // Initialize f with a legitimate function dealing Parent 
f(x); 

Now imagine two classes:

class Child1: public Parent {
    public:
        void say() const {
            std::cout<<"I am child1"<<"\n";
        }
        virtual void tell() const {
            std::cout<<"This is specific to child1"<<"\n";
        }
};
class Child2: public Parent {
    public:
        void say() const {
            std::cout<<"I am child2"<<"\n";
        }
};

The following code would also be valid, since Child1 and Child2 derive from Parent:

Child1 y; 
Child2 z; 
f(y);   
f(z);

If you were allowed to assign a function with a child argument instead of a parent argument for your wrapper, you could as well do something like:

Wrapper f=[](const Child1& child){  // if this is legitimate
  child.tell();                     //   then this would be legitimate
};

And you'll easily guess that f(x) and f(z) would not work although the type of f should allow it.

Is there a work-around?

What you can do, but this is something more risky, is to make a wrapper function that takes a Parent argmument and down-casts is to a Child. But I'd not recommend it unless there's no other solution and only with extra-care.

using Wrapper = std::function<void(const Parent&)>;

int main() {
    Wrapper func=[](const Parent& parent){
      auto child=dynamic_cast<const Child*>(&parent);  
      if (child)
        child->say();
      else std::cout<<"OUCH!!! I need a child"<<std::endl; 
    };
    
    Parent x; 
    Child c;
    func(c);
    func(x); 
}

Demo

  • Related