Okan Barut
Okan Barut

Reputation: 319

Call member function of an object stored in std::any

Suppose that I have a templated class, like so:

template<class T, class V>
class MyClass
{
    public:
    MyClass(std::string const& name){
        s = name;
    }

    T return_var1() {
        return var1;
    }

    std::string s;
    T var1;
    V var2;
};

And I'm going to use it as shown:

class AnotherClass {
    public:

    void some_func() {
        MyClass<int, double> my_var("test");
        std::pair<std::string, std::any> tmp_pair("test", my_var);
        my_vars.insert(tmp_pair);
    }

    void some_other_func() {
        auto tmp = my_vars["test"];
        
        // Any way to call member function without using std::any_cast<...> ?
        // For example, not like this:
        // (std::any_cast<MyClass<int, double>>tmp).return_var1()
        // But like this:
        std::cout << tmp.return_var1();
    }

    private:
    std::unordered_map<std::string, std::any> my_vars;
}

Is there any way to call member function of an object stored in std::any without knowing or using the type of the contained object? (without using std::any_cast<>() as written on the comments above)

Any solution is acceptable, e.g. inheriting MyClass from a base class with CRTP, using std::variant and so on.

The reason I need this is because I want to use a templated function as such:

some_templated_function<decltype(my_var.var1)>();

// Or similarly,
some_templated_function<decltype(my_var.return_var1())>();

The purpose is to allow the user to register the types <int, double> only once, and use the typenames extensively, wherever needed.

Upvotes: 2

Views: 749

Answers (1)

davidhigh
davidhigh

Reputation: 15468

Yes, this is possible as long as the function that should be called has a known signature. I'm going to describe things using a more simple example as the one in your OP. Let's assume you have two classes that provide a member function greet with a fixed signature:

struct A
{
    void greet() const { std::cout<< "hello" << std::endl; }
};

struct B
{
    void greet() const { std::cout<< "goodbye" << std::endl; }
};

Using std::any, you can wrap any instances of these classes:

std::any a = A();
a = B();

In order to use the contained objects, however, you always have to know the exact type that is currently contained. This is unfortunate if you have numerous types or if the types are not defined by yourself (as in a library, for example). The basic approach to overcome this requirement is given in the following code:

struct any_caller
{
    template<typename T>
    any_caller(T object)
        : any_(object)
        , greet_([](std::any const& a){ return std::any_cast<T const&>(a).greet();})
    {}

    std::any any_;
    void (*greet_)(std::any const&);
    auto greet() const { return greet_(any_); }
};

The idea is the following:

  • Define a function pointer with the exact signature of the desired function to call. This will point to a lambda which performs the actual call of the function in the any-object.
  • At the time of construction, and only there, you know the type of the object that is passed to std::any. You can use this information to set up a lambda that performs the std::any_cast and thereafter calls the desired function.
  • This is a form of type erasure, as you are abstracting away from the actual object type that is stored, and, again, only works because you know the exact signature you want to call.

The code above can then be used as follows:

int main()
{
    any_caller c = A();
    c.greet();  //prints "hello"

    c = B();
    c.greet();  //prints "goodbye"
}

Demo on Godbolt

This basic example can further be extended to support more than one function, leading to a technique that has been called Inheritance without pointers described here.

Upvotes: 1

Related Questions