Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Non-pointer typedef of member functions not allowed?

After getting an answer to this question I discovered there are two valid ways to typedef a function pointer.

typedef void (Function) ();
typedef void (*PFunction) ();

void foo () {}

Function * p = foo;
PFunction  q = foo;

I now prefer Function * p to PFunction q but apparently this doesn't work for pointer-to-member functions. Consider this contrived example.

#include <iostream>

struct Base {
    typedef void (Base :: *Callback) ();
                        //^^^ remove this '*' and put it below (i.e. *cb)
    Callback cb;

    void go () {
        (this->*cb) ();
    }

    virtual void x () = 0;

    Base () {
        cb = &Base::x;
    }
};

struct D1 : public Base {
    void x () {
        std :: cout << "D1\n";
    }
};

struct D2 : public Base {
    void x () {
        std :: cout << "D2\n";
    }
};  

int main () {
    D1 d1;
    D2 d2;
    d1 .go ();
    d2 .go ();
}

But if I change it to the new preferred style: typedef void (Base :: Callback) () and Callback * cb, I get a compiler error at the point of typedef

extra qualification 'Base::' on member 'Callback'

Demo for error.

Why is this not allowed? Is it simply an oversight or would it cause problems?

like image 940
spraff Avatar asked Sep 15 '11 10:09

spraff


2 Answers

For non-member functions, a type such as typedef void(Function)() has several uses, but for member functions the only application is to declare a variable which holds a function pointer. Hence, other than a stylistic preference, there's no strict need to allow this syntax and it has been omitted from the standard.

Background

The :: is a scope resolution operator, and the syntax X::Y is reserved for static member access if X is a class type. So X::*Z was another syntax invented to define pointer-to-member.

Forget member-function for a while, just think about member-data, and see this code:

struct X
{
   int a;
};

int X::*pa = &X::a; //pointer-to-member
X x = {100}; //a = 100
cout << (x.*pa) << endl;

It defines a pointer-to-member-data, and the cout uses it to print the value of a of object x, and it prints:

100

Demo : http://www.ideone.com/De2H1

Now think, if X::pa (as opposed to X::*pa) were allowed to do that, then you've written the above as:

int X::pa = X::a; //not &X::a

Seeing this syntax, how would you tell if X::a is a static member or non-static member? That is one reason why the Standard came up with pointer-to-member syntax, and uniformly applies it to non-static member-data as well as non-static member-function.

In fact, you cannot write X::a, you've to write &X::a. The syntax X::a would result in compilation error (see this).


Now extend this argument of member-data to member-function. Suppose you've a typedef defined as:

typedef void fun();

then what do you think the following code does?

struct X
{
   fun a;
};

Well, it defines member a of type fun (which is function taking no argument, and returning void), and is equivalent to this:

struct X
{
   void a();
};

Surprised? Read on.

struct X
{
   fun a; //equivalent to this: void a();
};

void X::a() //yes, you can do this!
{
     cout << "haha" << endl;
}

We can use exactly the same syntax to refer to a which is now a member-function:

X x;
x.a(); //normal function call

void (X::*pa)() = &X::a; //pointer-to-member
(x.*pa)(); //using pointer-to-member

The similarity is the synatax on the right hand side : &X::a. Whether a refers to a member-function or member-data, the syntax is same.

Demo : http://www.ideone.com/Y80Mf

Conclusion:

As we know that we cannot write X::a on the RHS, no matter if a is a member-data or member-function. The only syntax which is allowed is &X::f which makes it necessary that the target type (on LHS) must be pointer as well, which in turn makes the syntax void (X::*pa)() absolutely necessary and fundamental, as it fits in with other syntax in the language.

like image 64
Nawaz Avatar answered Nov 14 '22 19:11

Nawaz


To be precise the two typedef's in the case of the non-member pointers are not the same:

typedef void function();
typedef void (*fptr)();

The first defines function as a function taking no arguments and returning void, while the second defines ftpr as a pointer to function taking no arguments and returning void. The confusion probably arises as the function type will be implicitly converted to a pointer type in many contexts. But not all:

function f;            // declares void f();
struct test {
   function f;         // declares void test::f()
};
void g( function f );  // declares g( void (*f)() ): function decays to pointer to function in declaration
g( f );                // calls g( &f ): function decays to pointer to function
void f() {}            // definition of f
// function h = f;     // error: cannot assign functions
function *h = f;       // f decays to &f
like image 27
David Rodríguez - dribeas Avatar answered Nov 14 '22 19:11

David Rodríguez - dribeas