static functions for callbacks

M

Michael Oswald

Hello all,

I'm working on a project where I came across some situations, where the GUI
library works with normal C callbacks. The code has been implemented by
many people, so I came across different versions of callbacks (see the code
below for 3 variants).

Variant1: a normal static member function is used to redirect the callback
to a member function
Variant2: a static member function is declared, but at the definition the
extern "C" is added
Variant3: a normal C function is used.

Obviously, it works on at least two compilers (gcc 3.3.3 and a Sun compiler,
I don't know the version), as the output is for all 3 cases "member
called", but I remember from a discussion that the first 2 variants are not
legal C++ code.

What does the standard say about this?

Code:

#include <iostream>

typedef void (*func)(void*);

class Foo
{
public:

Foo(bool val)
{
if(val)
{
m_func = &func1;
m_arg = (void*)this;
}
else
{
m_func = &func2;
m_arg = (void*)this;
}
}
Foo(func f) : m_func(f), m_arg((void*)this)
{}

void call()
{
m_func(m_arg);
}

void member()
{
std::cout << "member called" << std::endl;
}

static void func1(void* arg);

static void func2(void* arg);

private:

func m_func;
void* m_arg;
};


// Variant 1: normal static member
void Foo::func1(void* arg)
{
((Foo*)arg)->member();
}

// Variant2: static member with extern "C"
extern "C" void Foo::func2(void* arg)
{
((Foo*)arg)->member();
}

// Variant3: normal function
extern "C" void func3(void* arg)
{
((Foo*)arg)->member();
}


int main()
{
Foo foo1(true);
Foo foo2(false);
Foo foo3(func3);

foo1.call();
foo2.call();
foo3.call();

return 0;
}
 
V

Victor Bazarov

Michael said:
I'm working on a project where I came across some situations, where
the GUI library works with normal C callbacks.

That's a pretty vague statement to make in a C++ newsgroup. What
is a "normal C" callback? As soon as you bring the library to work
with a C++ linker, the "C callback" has no relevance _unless_ the
declaration of the callback has some "C" in it.
The code has been
implemented by many people, so I came across different versions of
callbacks (see the code below for 3 variants).

Variant1: a normal static member function is used to redirect the
callback to a member function

Usually works fine when the callback is declared as a function and
is set by passing a function pointer as an argument.
Variant2: a static member function is declared, but at the definition
the extern "C" is added

That should be no different than 'Variant1', since the Standard says
that "A C language linkage is ignored for the names of class members
and the member function type of class member functions" [dcl.link]/4
Variant3: a normal C function is used.

There can be no "normal C function" in a C++ program. There can be
a stand-alone (non-member) function whose language linkage declared
as 'extern "C"'. Is that what you mean?
Obviously, it works on at least two compilers (gcc 3.3.3 and a Sun
compiler, I don't know the version), as the output is for all 3 cases
"member called", but I remember from a discussion that the first 2
variants are not legal C++ code.

You cannot remember that because it's not true. You can remember
that you thought (or that somebody claimed) that those variants
were not legal. They are legal, however..
What does the standard say about this?

Code:

#include <iostream>

typedef void (*func)(void*);

class Foo
{
public:

Foo(bool val)
{
if(val)
{
m_func = &func1;
m_arg = (void*)this;

There is no need to cast. Conversion to 'void*' is implicit.
}
else
{
m_func = &func2;
m_arg = (void*)this;
}
}
Foo(func f) : m_func(f), m_arg((void*)this)
{}

void call()
{
m_func(m_arg);
}

void member()
{
std::cout << "member called" << std::endl;
}

static void func1(void* arg);

static void func2(void* arg);

private:

func m_func;
void* m_arg;
};


// Variant 1: normal static member
void Foo::func1(void* arg)
{
((Foo*)arg)->member();

While a C-style cast is OK, it would be better to use 'static_cast'.
}

// Variant2: static member with extern "C"
extern "C" void Foo::func2(void* arg)
{
((Foo*)arg)->member();
}

// Variant3: normal function
extern "C" void func3(void* arg)
{
((Foo*)arg)->member();
}


int main()
{
Foo foo1(true);
Foo foo2(false);
Foo foo3(func3);

foo1.call();
foo2.call();
foo3.call();

return 0;
}

All three cases are OK.

V
 
M

Michael Oswald

Victor said:
Variant2: a static member function is declared, but at the definition
the extern "C" is added

That should be no different than 'Variant1', since the Standard says
that "A C language linkage is ignored for the names of class members
and the member function type of class member functions" [dcl.link]/4

Ah, ok, that makes sense.
There can be no "normal C function" in a C++ program. There can be
a stand-alone (non-member) function whose language linkage declared
as 'extern "C"'. Is that what you mean?
Yup.

You cannot remember that because it's not true. You can remember
that you thought (or that somebody claimed) that those variants
were not legal. They are legal, however..

Ok, that's all I needed to know.
While a C-style cast is OK, it would be better to use 'static_cast'.

Of course. Just to illustrate how the code I'm working with was written.

All three cases are OK.

Thanks, Victor!
 
J

James Kanze

I'm working on a project where I came across some situations,
where the GUI library works with normal C callbacks.

You really should be more specific about what you mean by a
"normal C callback". Something like the argument to:

extern "C" void f( void (*pf)( void* ) ) ;

?
The code has been implemented by many people, so I came across
different versions of callbacks (see the code below for 3
variants).
Variant1: a normal static member function is used to redirect the callback
to a member function
Variant2: a static member function is declared, but at the definition the
extern "C" is added
Variant3: a normal C function is used.

There are no "variants". The type of the argument must
correspond to the type declared in the parameter. Language
linkage is part of the type. If the example is like the above,
the function passed to f must be `extern "C"', or the code
shouldn't compile. An `extern "C"' specification is ignored for
member functions, so the argument cannot be a member function.
Regardless.

Note that not only the linkage, but the actual number and types
of declared arguments and return values must also correspond.
Thus, for example, things like:

extern "C" void g( void*, int = 0 ) ;
extern "C" void h( void const* ) ;
extern "C" int i( void* ) ;

aren't allowed either (alghough all could be called with a
single void*, with the return value not used. The rule is very
strict; the exact types must match. (This may be
overspecification in the case of h, but in all of the other
cases, it's very easy to imagine exotic implementations where it
would fail.)

Note that forcing the type with a cast results in undefined
behavior, in all cases. The only thing you can do with a cast
pointer to function, other than copy it, is to cast it back the
original type. (Actually, of course, it only results in
undefined behavior if the C function calls your function without
casting the pointer back to the original type. But the
probability of that happening seems fairly high to me.)
Obviously, it works on at least two compilers (gcc 3.3.3 and a
Sun compiler, I don't know the version), as the output is for
all 3 cases "member called",

It must be a very old version of Sun CC, because current
versions of Sun CC complain. It's only a warning, since they
don't want to break code which worked with earlier versions, but
I don't think you can turn the warning off.

This is a known bug in g++. Judging from past history with g++,
it wouldn't surprise me if some future version simply declared
it an error, and refused to compile the code at all.
but I remember from a discussion that the first 2 variants are
not legal C++ code.
What does the standard say about this?

#include <iostream>
typedef void (*func)(void*);

Note that the type of func is pointer to a function with C++
language linkage. You cannot pass a pointer of type func to a C
program.
class Foo
{
public:
Foo(bool val)
{
if(val)
{
m_func = &func1;
m_arg = (void*)this;

The cast is unnecessary (and missleading---it suggests your're
playing games with the type system, which you aren't).
}
else
{
m_func = &func2;
m_arg = (void*)this;
}
}
Foo(func f) : m_func(f), m_arg((void*)this)
{}
void call()
{
m_func(m_arg);
}
void member()
{
std::cout << "member called" << std::endl;
}
static void func1(void* arg);
static void func2(void* arg);
private:
func m_func;
void* m_arg;
};
// Variant 1: normal static member
void Foo::func1(void* arg)
{
((Foo*)arg)->member();
}
// Variant2: static member with extern "C"
extern "C" void Foo::func2(void* arg)

The `extern "C"' is ignored on member functions, so writing it
here is missleading. The case is exactly the same as func1.
{
((Foo*)arg)->member();
}
// Variant3: normal function
extern "C" void func3(void* arg)
{
((Foo*)arg)->member();
}
int main()
{
Foo foo1(true);
Foo foo2(false);
Foo foo3(func3);

The last line above shouldn't compile. You're passing a
function with "C" linkage to a function which requires a pointer
to a function with "C++" linkage. With Sun CC (5.8), I get the
following error messages:

"linkage.cc", line 71: Warning (Anachronism): Formal argument f of
type void(*)(void*) in call to Foo::Foo(void(*)(void*)) is being
passed extern "C" void(*)(void*).
"linkage.cc", line 71: Warning (Anachronism): Using extern "C"
void(*)(void*) to initialize void(*)(void*).
2 Warning(s) detected.

(The "Anachronism" in the message is Sun's way of saying that
this is illegal, but because we accepted it in the past, we will
still compile the code. For the moment---perhaps not in some
future version.)

Think about it for a moment. The calling conventions for C and
for C++ are not necessarily identical---on an Intel, for
example, the most logical solution for C is different than the
most logical one for C++ (for historical reasons), and I've used
compilers for Intel where they were different.
foo1.call();
foo2.call();
foo3.call();
return 0;
}

I don't see where you have any C callback in your example, but
your code requires a diagnostic according to the standard.
Similarly, if you have a function declared like my f(), above,
the *only* function you can legally pass it is func3. Anything
else requires a diagnostic (and gives it with Sun CC).
 
J

James Kanze

That's a pretty vague statement to make in a C++ newsgroup. What
is a "normal C" callback? As soon as you bring the library to work
with a C++ linker, the "C callback" has no relevance _unless_ the
declaration of the callback has some "C" in it.

He probably means something like:
extern "C" void f( void (*)() ) ;
It's true that he didn't have anything like this in his example,
however.
Usually works fine when the callback is declared as a function
and is set by passing a function pointer as an argument.
That should be no different than 'Variant1', since the
Standard says that "A C language linkage is ignored for the
names of class members and the member function type of class
member functions" [dcl.link]/4
Variant3: a normal C function is used.
There can be no "normal C function" in a C++ program. There
can be a stand-alone (non-member) function whose language
linkage declared as 'extern "C"'. Is that what you mean?

You're missing a very important requirement: that the type of
the function pointer correspond to the type required by the
function it's passed to. This means that the type and number of
parameters, the type of the return, and the language linkage
must be identical. (I'm not too sure with regards to exception
specifications. But of course, they aren't relevant to a C
callback anyway.) If the type of the function pointer is not
the same as that required, then a diagnostic is required.

The language linkage IS part of the type. §7.5/1: "Two function
types with different language linkages are distinct types even
if they are otherwise identical."

An error with regards to type requires a compiler diagnostic.
We're not dealing here with undefined behavior, which the
compiler is free to defined; if a programmer passes a function
with the wrong linkage, a conforming compiler must issue a
diagnostic. (Both g++ and VC++ seem to be deficient in this
regard, but Sun CC definitly issues diagnostic, and I imagine
that most other compilers do as well. In the case of g++, it's
a known bug, and in the case of VC++, I don't know whether it's
a bug, or a case of intentionally deviating from the standard to
lock your code base in.)
You cannot remember that because it's not true.

He remembers correctly. The issue has been discussed here
before. Linkage is part of the type, and the types of an
argument must correspond to the types of the declared
parameters. (If the function is declared "void f( ... )", the
issue is more complex. But if the function uses va_args to
recover the argument, then the type specified within the
function must be the same as the type of the argument, or
undefined behavior results.)
You can remember that you thought (or that somebody claimed)
that those variants were not legal. They are legal, however..

If I understand correctly what he is asking, his first two
variants aren't legal, and are rejected by conforming compilers.

[...]
Just for the fun of it, I added the following here:

std::cout << typeid( &Foo::func1 ).name() << std::endl ;
std::cout << typeid( &Foo::func2 ).name() << std::endl ;
std::cout << typeid( &func3 ).name() << std::endl ;

The output from Sun CC is:
void(*)(void*)
void(*)(void*)
extern "C" void(*)(void*)

Note that his func3 (declared `extern "C"') has a different
type.
All three cases are OK.

No. (Not that his example corresponds in any way to the problem
he tried to explain. What he really needs is a:

extern "C" void caller( void (*f)( void* ), void* p )
{
(*f)( p ) ;
}

Given this, he can legally call caller with func3, but not with
any of the member functions as arguments.
 
M

Michael Oswald

James said:
You really should be more specific about what you mean by a
"normal C callback". Something like the argument to:

extern "C" void f( void (*pf)( void* ) ) ;

I had a look again and saw both version, callbacks which need the C linkage
as well as free functions with C++ linkage.
There are no "variants". The type of the argument must
correspond to the type declared in the parameter. Language
linkage is part of the type. If the example is like the above,
the function passed to f must be `extern "C"', or the code
shouldn't compile. An `extern "C"' specification is ignored for
member functions, so the argument cannot be a member function.
Regardless.

Ok, that's clear now.
Note that not only the linkage, but the actual number and types
of declared arguments and return values must also correspond.
Thus, for example, things like:

extern "C" void g( void*, int = 0 ) ;
extern "C" void h( void const* ) ;
extern "C" int i( void* ) ;

aren't allowed either (alghough all could be called with a
single void*, with the return value not used. The rule is very
strict; the exact types must match. (This may be
overspecification in the case of h, but in all of the other
cases, it's very easy to imagine exotic implementations where it
would fail.)

Note that forcing the type with a cast results in undefined
behavior, in all cases. The only thing you can do with a cast
pointer to function, other than copy it, is to cast it back the
original type. (Actually, of course, it only results in
undefined behavior if the C function calls your function without
casting the pointer back to the original type. But the
probability of that happening seems fairly high to me.)

Yes, and this makes sense to me.

It must be a very old version of Sun CC, because current
versions of Sun CC complain. It's only a warning, since they
don't want to break code which worked with earlier versions, but
I don't think you can turn the warning off.

Sun WorkShop 6 update 1 C 5.2 2000/09/11
So it's quite old.
This is a known bug in g++. Judging from past history with g++,
it wouldn't surprise me if some future version simply declared
it an error, and refused to compile the code at all.

Especially older gcc versions seem very tolerant to me.
Note that the type of func is pointer to a function with C++
language linkage. You cannot pass a pointer of type func to a C
program.

Yes, now the difference is clear to me.
The cast is unnecessary (and missleading---it suggests your're
playing games with the type system, which you aren't).
I don't see where you have any C callback in your example, but
your code requires a diagnostic according to the standard.
Similarly, if you have a function declared like my f(), above,
the *only* function you can legally pass it is func3. Anything
else requires a diagnostic (and gives it with Sun CC).

As I already wrote in the reply to Victor, this is code like it is found in
the commercial GUI library which is used, so I cannot change it. Obviously
it runs, since the system is in use.
But it's good to know that we can run into problems when we upgrade to a
newer compiler version.


Michael
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,579
Members
45,053
Latest member
BrodieSola

Latest Threads

Top