简体   繁体   中英

Pass Both Functions and Member-Function to Template Method

I've been looking into this issue for a while, I hope to get some clarification here.

I have developed a Template class. One of its methods takes for input two functions with different parameters, as follows:

template<typename S, typename T, typename R>
void TGraph<S,T,R>::BFS(const S &key, bool(*relValid)(R), void(*manip)(string, string, R)) {
    //Being here R,S members of the template class TGraph, do stuff including
    if(relValid("some R") ){
    manip("some S", "another S", "someR");
    }

}

This works like a charm when passing standard functions in the main.cpp.

int main(){
    TGraph2<string, User, Relation> graph;
    graph.BFS("Rube", alwaysValid, printRelation);
}
void printRelation(string id1, string id2, Relation rel){
    cout<<id1<<" ha una relazione con "<<id2<<" di tipo "<<rel.type<<endl;
}
bool alwaysValid(Relation rel){
    return true;
}

I cannot compile when using the template class in another class, and using member-functions instead.

class DataSet {
private:
    TGraph<string, User, Relation> _users;
....
}

I found out this is because of the difference between pointer-to-function and pointer-to-member-function types. A solutions is to make static the members, which is not possible in my case (I need to access some non-static members).``

Now I wonder, does exist some "clean" solution to allow BFS method to work with any function (member or not)? Or at least to make it work with member-functions (which actually is my need).

Member functions and non-member functions are called entirely different. As a result, they don't mix well. The approach to address this issue is to accept function objects instead of either function pointers or pointer to member functions is to accept function objects . These still won't necessarily accept member functions right away but carefully implementing the resulting function can deal transparently with all kinds of function objects.

To deal with function objects there are broadly two different approaches:

  1. Accept the function object as template parameter. Doing so is great for performance: the compilers happily inline all of the operations if they can see through the call chain (and passing lambda functions instead of function pointers makes that rather successful). The downside of this approach is that a function template needs to be exposed. Depending on the context that may be rather problematic. Also, the required signature isn't visible in the function signature and needs to specified elsewhere.

    You could write your BFS() member as below. The use of std::invoke() deal with passing passing anything which can be considered a function object including member function pointers and member pointers:

     template<typename S, typename T, typename R, typename RelValid, typename Manip> void TGraph<S,T,R>::BFS(const S &key, RelValid relValid, Manip manip) { //Being here R,S members of the template class TGraph, do stuff including if(std::invoke(relValid, "some R") ){ std::invoke(manip, "some S", "another S", "someR"); } } 
  2. Accept a type-erased function object which only specifies the function's interface. std::function<RC(T...)> is the obvious first choice for a type-erased function object. Function pointers, member function pointers, and function objects in general readily convert to compatible std::function<RC(T...)> types.

    Using std::function<...> to replace function pointer is straight forward: the function pointers already use the signature which is needed as template argument for std::function<...> :

     template<typename S, typename T, typename R> void TGraph<S,T,R>::BFS(const S &key, std::function<bool(R)> relValid, std::function<void(string, string, R)> manip) { //Being here R,S members of the template class TGraph, do stuff including if(relValid("some R") ){ manip("some S", "another S", "someR"); } } 

Which approach to choose depends somewhat on the expected use. If the function object is used heavily, eg, it is called once for each node visited, I'd prefer using a template argument. Where the function is used just a few times or it needs to show up in a non-templatised interface I'd use std::function<...> (or similar).

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