Search code examples
c++templatesvalue-typereference-typefunction-parameter

C++ function as a parameter where its parameter can be passed by value or by reference


I have the following functions where processArray is a generic function that is used to process an array. A function is passed to it as a parameter so that each element can also be processed.

template<typename TItem>
size_t processArray(const TItem* value, const size_t length, size_t (*processItem)(const TItem)) {
    size_t output = length;

    for (auto i = 0; i < length; ++i) {
        output += processItem(value[i]);
    }

    return output;
}

size_t processInt(const int value) {
    return value;
}

size_t processString(const std::string& value) {
    return value.length();
}

Then I want to process an array of int and std::string, but I encounter a compilation error (because (*processItem)(const TItem) does not accept a reference type parameter).

// Int
const int intArray[2] { 1, 2 };
const auto intArrayResult = processArray(intArray, 2, processInt); // OK

// String
const std::string strArray[2] { "123", "456" };
const auto strArrayResult = processArray(strArray, 2, processString); // Error: No matching function for call to 'processArray'

However, if I change the declaration of the processItem parameter to size_t (*processItem)(const TItem&) - make it accept the reference type, then I cannot pass processInt.

const int intArray[2] { 1, 2 };
const auto intArrayResult = processArray(intArray, 2, processInt); // Error: No matching function for call to 'processArray'

// String
const std::string strArray[2] { "123", "456" };
const auto strArrayResult = processArray(strArray, 2, processString); // OK

So, does anyone know how processArray can be written so that the function is declared only once and it can accept processItem that can process both value and reference types? Or is there no way to accomplish it?

// NG function declaration is repeated

template<typename TItem>
size_t processArray(const TItem* value, const size_t length, size_t (*processItem)(const TItem));

template<typename TItem>
size_t processArray(const TItem* value, const size_t length, size_t (*processItem)(const TItem&));

Solution

  • Just go templates all the way and make the function type a template as well:

    template<typename TItem, typename Func>
    size_t processArray(const TItem* value, const size_t length, Func func) {
        std::size_t output = length;
    
        for (std::size_t i = 0; i < length; ++i) {
            output += func(value[i]);
        }
    
        return output;
    }
    

    And you can see it working in this live example.

    One caveat with this approach is it will fail if your function is overloaded. If it is you will need to wrap it in a lambda expression like

    const auto intArrayResult = processArray(intArray, 2, [](const auto& val) { return overloaded_function(val); });
    

    If you are solely dealing with plain raw arrays you can also adjust the function to take the array by reference which allows you to not need to pass the size as well. It also allows you to use a ranged-based for loop. That would look like

    template<typename TItem, std::size_t N, typename Func>
    size_t processArray(const TItem (&values)[N], Func func) {
        std::size_t output = N;
    
        for (const auto& val : values) {
            output += func(val);
        }
    
        return output;
    }