Search code examples
c++functionpolymorphismsubclassstd-function

C++ std::function to take functions with sub class parameter


[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;
}

Solution

  • 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