Riko
Riko

Reputation: 453

Implicit type conversion in function pointers in C++?

I have 2 classes: Child derives from Parent:

#include <stdint.h>
#include <iostream>

using namespace std;

class Parent
{
public:
    int parentMember;
};

class Child : public Parent
{
};

Now, I have a class template for custom implementation of a dynamic array (unnecessary parts skipped)

template <typename T>
class DArray
{
private:
    T* m_array;
    int32_t m_length;
public:

    // Default constructor
    DArray() : m_array{ nullptr }, m_length{ 0 } {
    };

    // Search returns index of the first found item or -1 if not found, comparison is done 
    // using function pointer, which should return boolean
    int32_t Search(const T& data, bool(*comparisonFunction)(T, T)) {
        for (int32_t i = 0; i < m_length; i++) {
            if (comparisonFunction(m_array[i], data))
                return i;
        }
        return -1;
    }
};

I have a comparison function that will be used to find out if my dynamic array already contains an element with the same value of parentMember

bool comparisonFunction(Parent* n1, Parent* n2) {
    return (n1->parentMember == n2->parentMember);
}

Lastly, I have my dynamic array, which should hold pointers to Child objects.

int main()
{
    DArray<Child*> dArray;
    Child *c;
    dArray.Search(c, comparisonFunction);
    return 0;
}

This code returns error on this line:

dArray.Search(c, comparisonFunction);

The error is:

argument of type "bool (*)(Parent *n1, Parent *n2)" is incompatible with
parameter of type "bool (*)(Child *, Child *)"

My question is: Why doesn't the compiler implicitly convert Child* to Parent* as it does when I pass Child* as an argument to a function which takes Parent* as a parameter?

Is there any way how to solve this problem without implementing a new comparison function for every single child class?

Upvotes: 3

Views: 579

Answers (3)

Chase R Lewis
Chase R Lewis

Reputation: 2307

The compiler won't do this.

Generally it is best to submit functors as template parameters that way it will work with any item that could compile in that place. std::function and capturing lambda's for example would work with template arguments but not explicit function pointer declaration.

template <typename T>
class DArray
{
private:
    T* m_array;
    int32_t m_length;
public:

    // Default constructor
    DArray() : m_array{ nullptr }, m_length{ 0 } {
    };

    // Search returns index of the first found item or -1 if not found, comparison is done 
    // using function pointer, which should return boolean
    template<typename COMPARE>
    int32_t Search(const T& data,COMPARE& compareFunction) {
        for (int32_t i = 0; i < m_length; i++) {
            if (compareFunction(m_array[i], data))
                return i;
        }
        return -1;
    }
};

Upvotes: 0

The implicit conversion from Child * to Parent * is not necessarily a no-op. It can involve pointer arithmetic and even conditionals (for checking null). So while it's possible to call a function expecting a Parent * with an argument of type Child *, it's only possible because the compiler will insert any necessary conversion code in the point of the call.

This means that while you can convert a Child * into a Parent *, you cannot directly treat a Child * as a Parent *. However, you algorithm uses a pointer to a function of type bool(Child*, Child*). So it will pass two Child * objects into that function. At the site of the call through the function pointer, the compiler has no way of knowing that the pointer actually points to a bool(Parent *, Parent*) and that it should therefore insert conversion code from Child * to Parent * for each of the arguments.

No such code can be inserted at the site passing the pointer either. The compiler would effectively have to synthesise a wrapper of type bool(Child *, Child *), put the conversion code into it, and pass a pointer to that wrapper into Search. That would be a bit too expensive for a single implicit conversion.

The correct solution to your problem has already been given by other answers: take inspiration from the standard <algorithm> header and accept an arbitrary functor instead of a function pointer:

template <class F>
int32_t Search(const T& data, F comparisonFunction) {
    for (int32_t i = 0; i < m_length; i++) {
        if (comparisonFunction(m_array[i], data))
            return i;
    }
    return -1;
}

Upvotes: 2

aschepler
aschepler

Reputation: 72271

There are no implicit conversions between pointer to function types.

I would change your Search function to a template function which can take any functor type (including lambdas, std::function, etc.).

template <typename F>
int32_t Search(const T& data, const F& comparisonFunction) {
    for (int32_t i = 0; i < m_length; i++) {
        if (comparisonFunction(m_array[i], data))
            return i;
    }
    return -1;
}

Upvotes: 3

Related Questions