简体   繁体   中英

Template variadic function in template class won't compile

I'm trying to write a function for a template class which takes in a parameter that is a function pointer for a member class inside the private data of the big class. When you call that member, it calls that function on smaller class. (Confusing right?) To demonstrate, I have a non-working example here:

#include <vector>
#include <iostream>

using namespace std;

template <typename T, typename C>
struct MyClass {

    template <typename F, typename... A>
    auto call_me(F func, A... args) { // pass in the function we want to call
        return (mContainer.*func) (args...); // call the function supplied by 
        // the parameter on the private member data
    }

    C mContainer; // this will be private in my actual code

};


int main() {
    MyClass<int, std::vector<int> > test;;

    cout << test.call_me(&std::vector<int>::size) << endl; // works
    test.call_me(&std::vector<int>::insert, test.mContainer.begin(), 4); // doesn't work

    return 0;
}

Please note that this isn't my actual code but a small example of what I'm trying to do. As you can see, I'm trying to call the size member function of the 'Private' (I have kept it public here for demonstration) vector class inside MyClass . This only works whenever I have no parameters for the compiler to unpack, but when I try to do the insert function (which has parameters to unpack), the compiler gives me an error of:

.\template.cpp: In function 'int main()':
.\template.cpp:24:71: error: no matching function for call to 'MyClass<int, std::vector<int> >::call_me(<unresolved overloaded function type>, std::vector<int>::iterator, int)'
     test.call_me(&std::vector<int>::insert, test.mContainer.begin(), 4);
                                                                       ^
.\template.cpp:10:10: note: candidate: template<class F, class ... A> auto MyClass<T, C>::call_me(F, A ...) [with F = F; A = {A ...}; T = int; C = std::vector<int>]
     auto call_me(F func, A... args) { // pass in the function we want to call
          ^~~~~~~
.\template.cpp:10:10: note:   template argument deduction/substitution failed:
.\template.cpp:24:71: note:   couldn't deduce template parameter 'F'
     test.call_me(&std::vector<int>::insert, test.mContainer.begin(), 4);

This is the same error I'm getting in my actual production code, calling the variadic function with no parameters to unpack works, but if I give more than that, I get the same error message. This is my first real attempt to use Variadic templates, so any recommendation and help will be appreciated.

The problem here is that insert is an overloaded function. The compiler is not doing to try and resolve what overload you want in template argument deduction as there is no way for it to know. You have to cast the function to the type of the overload you want to use in order to give it a type. That would look like

using insert_func_t = std::vector<int>::iterator(std::vector<int>::*)(std::vector<int>::const_iterator, const int&);
test.call_me(static_cast<insert_func_t>(&std::vector<int>::insert), test.mContainer.begin(), 4);

In general it is

static_cast<return_type(class_name::*)(function_parameters)>(&class_name::function_name)

Another option would be to change the function a little and take a lambda that expresses what you want done. That would look like

template <typename T, typename C>
struct MyClass {

    template <typename F, typename... A>
    auto call_me(F func, A... args) { // pass in the function we want to call
        return func(mContainer, args...); // call the function supplied by 
        // the parameter on the private member data
    }

    C mContainer; // this will be private in my actual code
};

int main() {
    MyClass<int, std::vector<int> > test;;

    test.call_me([](auto& container, auto... args){ container.insert(args...); }, test.mContainer.begin(), 4);

    return 0;
}

Basically you cannot take address of an unresolved overloaded function, because the compiler won't be able to choose the right function entry point address. During normal function call the compiler resolves overloaded function, but with templates like yours or std::bind() this won't work, because the parameters are used to call the template function, not the function you want to take address of.

You can manually resolve the overload like this:

    using ftype = std::vector<int>::iterator(std::vector<int>::*)
        (std::vector<int>::const_iterator, const std::vector<int>::value_type&);
    test.call_me((ftype)(&std::vector<int>::insert), test.mContainer.begin(), 4); // works

It's easier to deal in function objects when doing this kind of thing. It offloads the problem of method overloads to the compiler.

Lambdas also work (they're function objects):

#include <vector>
#include <iostream>

template <typename T, typename C>
struct MyClass {

    template <typename F, typename... A>
    auto call_me(F func, A&&... args) -> decltype(auto)
    { // pass in the function we want to call
        return func(mContainer, std::forward<A>(args)...); // call the function supplied by 
        // the parameter on the private member data
    }

    C mContainer; // this will be private in my actual code

};
/*
 * It's often easier to deal in function objects
 */
struct insert
{
    template<class Container, class...Args>
    decltype(auto) operator()(Container& cont, Args&&...args) const
    {
        return cont.insert(std::forward<Args>(args)...);
    }
};

struct size
{
    template<class Container, class...Args>
    decltype(auto) operator()(Container& cont) const
    {
        return cont.size();
    }
};

int main() {
    MyClass<int, std::vector<int> > test;;

    std::cout << test.call_me(size()) << std::endl; // works
    test.call_me(insert(), test.mContainer.begin(), 4); // doesn't work

    // or lambdas
    auto insert2 = [](auto& container, auto&&...args) -> decltype(auto)
    {
        return container.insert(std::forward<decltype(args)>(args)...);
    };
    test.call_me(insert2, test.mContainer.begin(), 5); 


    return 0;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM