Search code examples
c++function-pointersvariadic-templatesperfect-forwarding

Perfect forwarding function parameters to function pointer: What about passing by value?


I have a data structure that provides access to its elements, and some loop logic for how to iterate over them. I need to call different functions inside the loop(s). These functions all take a data element as first parameter, but shall be allowed to have any number of additional parameters. So far, this sounds like a typical case for perfect fowarding, so my attempt is this (generic example):

template<typename ... Ts>
void looper(
    const DataStructure& dataStruct,
    void (*func)(const DataElement&, Ts ...),
    Ts&& ... args
){
    for (Index i{0}; i<dataStruct.someSize(); ++i )
        func( dataStruct.elem(i), std::forward<Ts>(args) ... );
}

However, say I want to call it with a function that takes parameters by value (like primitive types), then I quickly have a problem.

void myFunc( const DataElement&, int ){
    /* do something */
}

If I call the looper function and pass a variable of type int, it will always be recognised as an int&, and then I have an inconsistent parameter pack deduction with 'int' and 'int&':

DataStructure dataStruct;
int myInt {0};
looper( dataStruct, myFunc, myInt ); // <--- this line will cause a compiler error

That's a very descriptive error message, and I know I could, for example, solve it by making myFunc take a const int&.

However, I'd rather be able to just write any function, which also may take parameters by value, and pass its pointer to the looper. How can I achieve that?


Solution

  • Args will be deduced twice, once for the function signature, and once for the parameters. They do not necessarily need to be exactly the same: the argument is deduced as int&, since it is deduced from a local variable, but it does not correspond to the function signature (which is int).

    You can split them, like this:

    template<typename... Ts_fun_args, typename... Ts_param_args>
    void looper(
        const DataStructure& dataStruct,
        void (*func)(const DataElement&, Ts_fun_args ...),
        Ts_param_args&& ... args
    ){
        for (Index i{0}; i<dataStruct.someSize(); ++i )
            func( dataStruct.elem(i), std::forward<Ts_param_args>(args) ... );
    }
    

    Note that Ts_fun_args needs to be convertable to Ts_param_args. If you have a function signature taking a int& for example, you cannot pass a const int&. This might lead to a confusing error message.