Search code examples
c++templatesdecltyperesult-of

Deduce return type of member function


In a template function I was trying to create a std::vector with its value_type being dependent on a member function of the template parameter to the function. This template parameter is restricted to being a vector containing unique pointers of a certain type with a certain function. For example:

/* somewhere in the code */
std::vector< std::unique_ptr< Widget > > myVec;
/* work with myVec and fill it, then call the relevant function */
func(myVec);

Now the function func needs to retrieve the return type of the member function member_func of Widget. Note that Widget can also be a different type, as long as it has the member function member_func.

template <typename Vec>
void func(const Vec& vec) {
  using ret_type = decltype(Vec::value_type::element_type::member_func()); // Doesn't work
  std::vector< ret_type > local_vec;
}

I've tried various things, e.g. std::result_of, std::invoke_result and decltype, but I can't seem to make it work. Is this even possible and if yes, how could it be achieved?


Solution

  • Is this close to what you wanted?

    #include <vector>
    #include <utility>
    #include <memory>
    
    struct Foo
    {
        int member_func();
    };
    
    template <typename Vec>
    void func(const Vec& vec) {
    
        using ret_type = decltype(std::declval<typename Vec::value_type>()->member_func());
    
        std::vector< ret_type > local_vec;
    }
    
    
    int main()
    {
        std::vector<std::unique_ptr<Foo>> v;
        func(v);
    }
    

    Demo: https://godbolt.org/g/dJkSf1

    Explanation:

    std::declval<typename Vec::value_type>() generates a reference to a unique_ptr (which must be used in an unevaluated context). We then take the decltype of calling generated_reference->member_function().

    This would be the same type as the result of vec[0]->member_func()

    Indeed, we could write it this way:

    template <typename Vec>
    void func(const Vec& vec) {
    
        using ret_type = decltype(vec.at(0)->member_func());
    
        std::vector< ret_type > local_vec;
    }
    

    Which might be more expressive and generic (Vec may now be any type which is vector-like and holds pointer-like things to Foo)

    Furthermore, the more generically we approach the deduction, the more generic our func function becomes:

    #include <vector>
    #include <utility>
    #include <memory>
    #include <set>
    #include <iterator>
    
    struct Foo
    {
        int member_func();
    };
    
    template <typename Vec>
    void func(const Vec& vec) {
    
        using ret_type = decltype((*std::begin(vec))->member_func());
    
        std::vector< ret_type > local_vec;
    }
    
    
    int main()
    {
        std::vector<std::unique_ptr<Foo>> v;
        func(v);
        func(std::array<std::unique_ptr<Foo>, 10> { });
    
        Foo* foos[] = { nullptr, nullptr };
        func(foos);
    
        func(std::set<std::shared_ptr<Foo>, std::owner_less<>> {});
    }
    

    Note

    This code assumes that the return_type of Foo::member_func is not a reference type.

    If that's a possibility, we'd need to decide whether we used metaprogramming to:

    a) convert reference types to std::reference_wrapper, so they can be stored in a vector, or

    b) convert reference types to fundamental types using std::decay, which would result in copies being made.