1

I'm learning C++ and I keep getting a weird compilation error I don't understand. I simplified my code to the following example:

#include <iostream>

class A {
public:
    static A foo()
    {
        A a;
        return a;
    }
};

class B : A
{
public:
    static B boo()
    {
        B b;
        return b;
    }
};

typedef A (*function)(void);

int main() {
    function f1 = A::foo;
    function f2 = B::boo; //error
    return 0;
}

I get the following error:

Cannot initialize a variable of type 'function' (aka 'A (*)()') with an lvalue of type 'B ()': different return type ('A' vs 'B')

Would anyone be able to explain why it doesn't work? Since B is a child of A, B is A, why is a function pointer from "void to B" isn't like a function pointer from "void to A"?

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
2292
  • 65
  • 7
  • 2
    The return types are different. B is not A – 273K Jun 12 '22 at 21:01
  • 1
    Apart from the answers, nobody in the whole program knows that `B` inherits from `A`, because default access modifier for `class` is `private`. You need `class B: public A` – Yksisarvinen Jun 12 '22 at 21:03

3 Answers3

4

Your function claims that it should return an A but boo returns a B, so:

typedef A (*A_function)();
typedef B (*B_function)();

int main() {
    A_function f1 = A::foo;
    B_function f2 = B::boo;
}

You could possibly solve it by returning a base class pointer from both functions:

class A {
public:
    virtual ~A() = default;      // for destruction via a base class pointer

    static A* foo() { return new A; }
};

class B : A { // you should probably use public inheritance
public:
    static A* boo() { return new B; } // creates a B but returns an A*
};

typedef A* (*function)();

int main() {
    function f1 = A::foo;
    function f2 = B::boo;
}

Or better, using smart pointers to not have to delete instances manually:

#include <memory>

class A {
public:
    virtual ~A() = default;

    static std::unique_ptr<A> foo() { return std::make_unique<A>(); }
};

class B : public A {
public:
    static std::unique_ptr<A> boo() { return std::make_unique<B>(); }
};

using function = std::unique_ptr<A>(*)();

int main() {
    function f1 = A::foo;
    function f2 = B::boo;
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
4

It is true that C++ allows a pointer to a child class to get converted to a pointer to the parent class (subject to certain conditions).

It is also true that C++ allows a reference to a child class to get converted to a reference to the parent class (also subject to certain conditions).

However, that does not mean that C++ allows a pointer to a function that returns a parent class to be converted to a pointer to a function that returns the child class. This is what the shown code is trying to do, and that is not allowed.

That answer your question of "why": because this is not allowed in C++. As far as why, the "why"; that's mostly due to some practical considerations, but that's immaterial, that's just not allowed by the rules of C++.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • 1
    As for the practical considerations, consider the case where `B` has an added data member to `A`. Now the copy constructors for the two classes will be different; which should the compiler call and how would it choose? – Mark Ransom Jun 12 '22 at 21:24
0

Since B is a child of A, B is A,

Not quite. Since B is a child of A, any B object is also an A object. However, this is not symmetric, so it is overstating the case to claim that B is A. It is correct to claim "a B is an A", but dropping the indefinite articles ("a" and "an") makes the claim false.

why is a function pointer from "void to B" isn't like a function pointer from "void to A"?

Because B is not A. The return types are different, so the function types are different. A B object can be converted to an A object (by copying the A sub-object), but that is an extra step. If you were to write

A value = B::boo();

then the compiler inserts a conversion from B to A after boo() is called. The conversion might not require machine instructions (a.k.a. a "no-op"), but it is there nonetheless. (One side-effect of having a conversion is that copying cannot be elided.)

A value = static_cast<A&&>(B::boo());

If you were to invoke boo() through a function pointer of the type A(*)(), there would be nothing to tell the compiler to insert the conversion. Skipping the conversion could be an issue, particularly if a B object had data members not in A objects, especially if those data members had non-trivial destructors.

The language does not attempt to draw a line between the cases where skipping the conversion is and is not a problem. This applies to both the return type and the parameter types. The type of a function assigned to a pointer must match the pointer's type exactly, no conversions needed.


This is where function objects, like std::function, are useful. A std::function<A()> could store either A:foo or B::boo. This is because the function object will adapt and record what conversions are needed, similar to what happens when invoking a function directly. Unlike a function pointer, a function object does not require an exact match on types.

JaMiT
  • 14,422
  • 4
  • 15
  • 31