4

I have the following code which allows me to instantiate and then call a list of void() functions.

(I am using https://github.com/philsquared/Catch for unit testing if you wish to compile and run this code).

#include "catch.hpp"

#include <functional>
#include <vector>

class ChainOfResponsibility : public std::vector<std::function<void()> >, public std::function<void()>
{
public:
    void operator()() const
    {
        for(std::vector<std::function<void()> >::const_iterator it = begin(); it != end(); ++it) {
            (*it)();
        }
    }
};

TEST_CASE("ChainOfResponsibility calls its members when invoked")
{
    bool test_function_called = false;
    std::function<void()> test_function = [&]()
    {
        test_function_called = true;
    };

    ChainOfResponsibility object_under_test;

    object_under_test.push_back(test_function);
    object_under_test();

    REQUIRE(test_function_called);
}

My question is how do I template the ChainOfResponsibility class to accept functions with a different (but consistent) signature?

For example, consider a ChainOfResponsibility<void(int)> or a ChainOfResponsibility<ReturnClass(Argument1Class, Argument2Class)>.

For the sake of argument, lets say that the second example returns the value returned by the last member in the chain, or the default value for ReturnClass if the chain is empty.

Also, if the STL already contains a template class that achieves this, then I would prefer to use it over my home-grown class.

spierepf
  • 2,774
  • 2
  • 30
  • 52
  • Since you are using `std::function`, your code requires C++11, so you could also use `auto` and range-based `for`. That would allow much cleaner code. – el.pescado - нет войне Aug 18 '17 at 09:01
  • 2
    why do you need to derive from `std::vector` and `std::function`? – Andriy Tylychko Aug 18 '17 at 09:21
  • @el.pescado Thanks for the tip! I haven't programmed in C++ in some time. So I'm not aware of the newer features. – spierepf Aug 18 '17 at 11:32
  • @Gruffalo I derive from std::vector to get all the vector operations for free (like push_back, insert, and swap). I derive from std::function so that instances are nestable. If there is a better way, I am all ears... – spierepf Aug 18 '17 at 11:37
  • @Gruffalo Turns out, I don't need to derive from std::function to make instances nestable. Thanks for the eye opener! – spierepf Aug 18 '17 at 12:16
  • @spierepf I don't know all your details of course but I do think there's a better way, check the last suggestion in Akira's answer. all you need is a function, inheritance is massively overused in general – Andriy Tylychko Aug 18 '17 at 12:19

2 Answers2

7

Your specific "discard all the intermediate results" is also fairly simple, but I think it's a bad idea.

template<typename Ret, typename ... Args>
class ChainOfResponsibility
{
    std::vector<std::function<Ret(Args...)> > chain;
public:
    Ret operator()(Args ... args) const
    {
        Ret value;
        for(auto & func : chain) {
            value = func(args...);
        }
        return value;
    }
};

void has to be treated on it's own

template<typename ... Args>
class ChainOfResponsibility<void, Args...>
{
    std::vector<std::function<void(Args...)> > chain;
public:
    void operator()(Args ... args) const
    {
        for(auto & func : chain) {
            func(args...);
        }
    }
};

Note that deriving from std:: types is a bad idea, especially std::function, which is a type-erasing callable, not "the base of all callables". You can simply provide an operator()

options for improving the non-void case:

    // fold the results
    template <typename BinaryFunction>
    Ret operator()(Args ... args, BinaryFunction add, Ret init) const
    {
        for(auto & func : chain) {
            init = add(init, func(args...));
        }
        return init;
    }

    // return a vector
    template <typename BinaryFunction>
    std::vector<Ret> operator()(Args ... args) const
    {
        std::vector<Ret> results(chain.size());
        for(auto & func : chain) {
            results.push_back(func(args...));
        }
        return results;
    }
Caleth
  • 52,200
  • 2
  • 44
  • 75
1

You don't need to use the std::function as a base class, using std::vector is sufficent. The template ChainOfResponsibility can use the same template paramter list as the std::function like follows:

#include <iostream>
#include <string>
#include <functional>
#include <vector>

template<typename>
class ChainOfResponsibility;

template<typename R, typename... Args>
class ChainOfResponsibility<R(Args...)> :
        public std::vector<std::function<R(Args...)>> {
public:
    R operator()(const Args&... args) {
        R result {};

        for(auto it = this->begin(); it != this->end(); ++it)
            result = (*it)(args...);

        return result;
    }
};

int main() {
    ChainOfResponsibility<std::string(int, int)> tester;

    tester.push_back([](int a, int b)->std::string {
        return std::to_string(a + b);
    });

    std::cout << tester(4, 2) << std::endl;
}

Anyway, using std::vector only is good enoug for the problem you described. If the content of the overloaded operator() is nothing special, you can change my example above as follows:

int main() {
    std::vector<std::function<std::string(int, int)>> tester;

    tester.push_back([](int a, int b)->std::string {
        return std::to_string(a + b);
    });

    std::string result;

    for(auto& test_fn : tester)
        result = test_fn(4, 2);

    std::cout << result << std::endl;
}

You also can write a function template instead of overloading the operator():

template<typename R, typename... Args>
R perform(const std::vector<std::function<R(Args...)>>& functions,
        const Args&... args) {
    R result {};
    for(auto& test_fn : functions)
        result = test_fn(4, 2);
    return result;
}
Akira
  • 4,385
  • 3
  • 24
  • 46