Search code examples
c++templateslambdageneric-programming

Best practice for passing multiple function arguments in C++


I am wondering what the best practice for passing multiple function arguments in C++ is. I want to follow proper generic design principles as seen in the STL.

I have a generic algorithm function. Using templates, it allows the client to select what data structure will be used for running the algorithm and what container implementation the data will be drawn from. However, in order for it to be reusable, I have selected a number of places where client-designed code should be inserted.

template<class CustomContainerIterator, class Queue, class Element>
void run_generic_algorithm(CustomContainerIterator container,
                           std::function<void(Element)> client_code1,
                           std::function<void(Element)> client_code2,
                           std::function<void(Element)> client_code3)
{
    Queue queue;
    //Push things iterated through onto the queue
    Element queue_element = queue.top();
    client_code1(queue_element);
    // Run code modifying queue
    client_code2(queue_element);  
    // Run code modifying queue
    client_code3(queue_element);    
}

The client_code() functions are specified by the client and may modify the same variables owned by the client. In my code, there are 5 client functions. I originally passed them all as lambda expressions and captured the client variables I wanted to modify. However, this makes for a very verbose function call.

Most of the time I am not even specifying all the client functions, so I have to put in a lambda function that does nothing. Is there a better way, of achieving what I want. I would like to stick to proper generic programming principles?


Ok so I have read the answers. Could I combine the ideas like this? Now I don't think I need virtual functions.

template <typename Element>
struct Customization {
    void client_code1(Element) {}
    void client_code2(Element) {}
    void client_code3(Element) {}
    void client_code4(Foo) {}
    void client_code5(Bar) {}
};

template <typename Element>
struct Implementation : public Customization {
    Data some_data;
    void client_code2(Element) {
        //Fill some_data with information.
    }
};

template<class Queue, class CustomContainerIterator, class CustomImplementation>
void run_generic_algorithm(CustomContainerIterator container,
                           CustomImplementation implementation)
{
    Queue queue;
    //Push things iterated through onto the queue
    auto queue_element = queue.top();

    implementation.client_code1(queue_element);
    // Run code modifying queue
    implementation.client_code2(queue_element);  
    // Run code modifying queue
    implementation.client_code3(queue_element);    
}

With usage

std::vector<int> v;
Implementation <int> custom;
run_generic_algorithm<std::queue<int>>(v, custom);

Solution

  • there are 5 client functions.

    You might pass structure instead

    template <typename Element>
    struct Customization {
        std::function<void(Element)> client_code1;
        std::function<void(Element)> client_code2;
        std::function<void(Element)> client_code3;
        std::function<void(Foo)> client_code4;
        std::function<void(Bar)> client_code5;
    };
    

    or even possibly an interface:

    template <typename Element>
    struct Customization {
        virtual ~Customization() = default;
        virtual void client_code1(Element) = 0;
        virtual void client_code2(Element) = 0;
        virtual void client_code3(Element) = 0;
        virtual void client_code4(Foo) = 0;
        virtual void client_code5(Bar) = 0;
    };
    

    and then

    template<class Range, class Element>
    void run_generic_algorithm(Range& r, const Customization <Element>& customization);
    

    Most of the time I am not even specifying all the client functions, so I have to put in a lambda function that does nothing.

    With previous comment, you might give default values to the member of previous structure.

    Else you can add default value to argument or create overloads.