简体   繁体   中英

C++ template function that takes a function type with specific return type

I am doing some magic with templates in c++ and thought of trying something.

In this case I wrote a naive generic list implementation with a List Type and a ListElement Type that contains the data.

Now I already wrote a templated "for each call" function, that takes an arbitrary member function type of the type stored in the list with an arbitrary argument list and calls that member function on each element in the list with the given arguments:

template<typename function, typename ... arguments>
void for_each_call(function call, arguments ... args)
{
    for(ListElement * current = this->first; current != nullptr; current = current->next)
    {
        (current->value->*call)(args ...);
    }
}

The problem with this is that I can not "react" to the return values of the called function. Though I do NOT want to implement a .map functionality!

Now I want to implement a "for each call until" that calls a function upon the values in the list until a call returns "true" and then stops. For that I need to limit the functions inserted as a template parameter to functions of any type that specifically return a boolean. I typed around until the compiler stopped complaining and got this:

template<bool (*function), typename ... arguments>
void for_each_call_until(arguments ... args)
{
    for(ListElement * current = this->first; current != nullptr; current = current->next)
    {
        if((current->value->*function)(args ...)) break;
    }
}

What is happening here, is this the right way, and if not, what is the right way?

EDIT: As some people recommend using functions from the std:: namespace: In these little training sessions I try to avoid the std:: like a plague, as if I would want to use std:: I would not write these little standardimplementations of stuff like lists, vectors or mappings myself but use std:: or boost::

First of all, this approach is unnecessarily limiting:

(current->value->*call)(args ...);

If you require a member function, there's only a small handful of operations you can actually do. If the caller wants to do more, they're kind of screwed. Instead, generalize and pass current->value as the first argument:

template<typename function, typename ... arguments>
void for_each_call(function call, arguments ... args)
{
    for(ListElement * current = this->first; current != nullptr; current = current->next)
    {
        call(current->value, args...);
    }
}

This works on all the cases as before - where you would pass &Class::mem before now instead you pass std::mem_fn(&Class::mem) - but now you can pass any kind of callable too.


Now onto your main question. You don't have to do anything different. Just use the result of call() :

template<typename function, typename ... arguments>
void for_each_call(function call, arguments ... args)
{
    for(ListElement* current = this->first; current != nullptr; current = current->next)
    {
        if (call(current->value, args...)) {
            break;
        }
    }
}

That's it. If the user provides a callable that doesn't return something contextually convertible to bool , they'll get a compile error. Why limit to just returning bool ?

If you really do need really just bool , throw in a static assert:

template<typename function, typename ... arguments>
void for_each_call(function call, arguments ... args)
{
    static_assert(std::is_same<decltype(call(this->first->value, args...)), bool>::value, "Must be bool!");
    // rest...
}

Note: You probably want to take your arguments... by reference to const to avoid lots of copies.

Starting from this, just to show you how member function pointers work :

class Foo {
 public:
  bool test() { return true; }
};

/// The function takes a member function of a class T and its arguments.
template<typename T, typename... Args>
void for_each_call_until(bool (T::*member_function)(Args...),
                         Args&& ... args) {
  T obj;  // Instantiate an example object.
  bool rts = (obj.*member_function)(std::forward<Args>(args)...);
  if (rts == false) {  // Check for the result of the member function
    // close
  }
  // ...
}

Your function could be something like:

template<typename... Args>
void for_each_call_until(bool (ListElement::*member_function)(Args...),
                         Args&& ... args) {
  for ( /* iteration over pointers */ ) {
    bool rts = (current->*member_function)(std::forward<Args>(args)...);
    if (rts == false) {
      // break
    }
    // ...
}
}

A simple solution involves using partial specialization to force a compilation error:

#include <type_traits>
#include <utility>

template<typename T> struct is_bool;

template<> struct is_bool<bool> { typedef int value; };

template<typename function, typename ... arguments>
void for_each_call(function call, arguments && ... args)
{
    typedef decltype(call(std::forward<arguments>(args)...)) ret_type;

    typedef typename is_bool<ret_type>::value x;

    call(std::forward<arguments>(args)...);
}

bool foo(int, int) {}   // Compiles

// int foo(int, int) {}   // Does not compile

int main()
{
    for_each_call(foo, 4, 2);
    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