Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Function type syntax confusion

38 views
Skip to first unread message

Juha Nieminen

unread,
May 31, 2020, 12:13:55 PM5/31/20
to
It's still unclear to me what the difference is between the function type
syntax of the form "ReturnType(*)(ParameterTypes)" and
"ReturnType(ParameterTypes)".

Sometimes they seem to be interchangeable, sometimes they don't.

For example, this is valid:

//-------------------------------------------
using F1 = int(*)(int);
using F2 = int(int);

void foo1(F1 funcPtr); // ok
void foo2(F2 funcPtr); // ok
//-------------------------------------------

However, this is not valid:

//-------------------------------------------
F1 funcPtr1 = someFunc; // ok
F2 funcPtr2 = someFunc; // error
//-------------------------------------------

Likewise:

//-------------------------------------------
std::function<int(int)> funcObj1; // ok
std::function<int(*)(int)> funcObj2; // error

std::set<int, bool(*)(int, int)> set1; // ok
std::set<int, bool(int, int)> set2; // error
//-------------------------------------------

So they are like the reverse of each other.

F2 above can be used for a function declaration, which might give some
insight into what it means. In other words:

//-------------------------------------------
F2 someFunction; // ok

int main()
{
int value = someFunction(5); // ok.
}

int someFunction(int v) { return v+1; }
//-------------------------------------------

Ben Bacarisse

unread,
May 31, 2020, 12:49:23 PM5/31/20
to
Juha Nieminen <nos...@thanks.invalid> writes:

> It's still unclear to me what the difference is between the function type
> syntax of the form "ReturnType(*)(ParameterTypes)" and
> "ReturnType(ParameterTypes)".

The first is a pointer type and the second a function type.

> Sometimes they seem to be interchangeable, sometimes they don't.
>
> For example, this is valid:
>
> //-------------------------------------------
> using F1 = int(*)(int);
> using F2 = int(int);
>
> void foo1(F1 funcPtr); // ok
> void foo2(F2 funcPtr); // ok

Because functions can't be passed as parameters, function types are
converted to pointer-to-function types in this context. This is
annoying, but it's hang-over from C.

It's analogous to (raw) array types in parameter lists -- they also get
converted to pointer types.

> //-------------------------------------------
>
> However, this is not valid:
>
> //-------------------------------------------
> F1 funcPtr1 = someFunc; // ok
> F2 funcPtr2 = someFunc; // error
> //-------------------------------------------

And you can't assign to a function.

> Likewise:
>
> //-------------------------------------------
> std::function<int(int)> funcObj1; // ok
> std::function<int(*)(int)> funcObj2; // error

You need a function type, not a pointer type, for std::function.

> std::set<int, bool(*)(int, int)> set1; // ok
> std::set<int, bool(int, int)> set2; // error
> //-------------------------------------------
>
> So they are like the reverse of each other.

A set hold data objects, and pointers are data objects so a set of
pointers makes sense. Functions are not considered to be data objects
in either C or C++ so you can't put them into collections.

> F2 above can be used for a function declaration, which might give some
> insight into what it means. In other words:
>
> //-------------------------------------------
> F2 someFunction; // ok

Yes, F2 is a function type and can be used to declare (but not define)
functions.

> int main()
> {
> int value = someFunction(5); // ok.
> }
>
> int someFunction(int v) { return v+1; }
> //-------------------------------------------

--
Ben.

Alf P. Steinbach

unread,
May 31, 2020, 9:08:34 PM5/31/20
to
On 31.05.2020 18:13, Juha Nieminen wrote:
> It's still unclear to me what the difference is between the function type
> syntax of the form "ReturnType(*)(ParameterTypes)" and
> "ReturnType(ParameterTypes)".

Ben Bacarisse has already answered this, and some of the following, but
I'll try to add a higher level perspective.

As he noted the * notation declares a pointer to function, while the
notation without that, declares a function.

Functions and function pointers are often interchangeable, just as
arrays and array item pointers are often interchangeable, because

* they have implicit conversion to pointer where a pointer is needed,
called a type DECAY, and
* the main functionality is only defined for pointers.

This is understandable given the history of C designed as a kind of
portable assembly language to ease the porting of Unix. In assembly
language everything is done via single values and pointers. With that
perspective it's the pointers that are primary, and the function and
array types are only sort of helpers to type check the pointer usage
(and for arrays, to allocate storage for automatic variables).

For example, there is no such thing as indexing of an array: formally
indexing is defined for pointers only.

And for example, there is no such thing as a call of a function: the
call syntax is available for pointers only.

Indexing and calling works for actual arrays and functions only because
of the implicit conversions to pointer.


> Sometimes they seem to be interchangeable, sometimes they don't.
>
> For example, this is valid:
>
> //-------------------------------------------
> using F1 = int(*)(int);
> using F2 = int(int);

Yes, but you can't declare a variable of type F2. If you try to then the
compiler understands that as a function declaration, not a variable.

Because functions are not data in C++. Or from a higher level
perspective, C++ is designed to support Harvard architecture machines
where code lives in a separate address space. So there is no such thing
as a variable of function type.

However, you can use F2 as a parameter type. Just as with an array type
it then decays. The type that F2 as parameter type expresses, is a pointer.


> void foo1(F1 funcPtr); // ok
> void foo2(F2 funcPtr); // ok


Yes, these mean exactly the same, due to the decay of the F2 type.


> //-------------------------------------------
>
> However, this is not valid:
>
> //-------------------------------------------
> F1 funcPtr1 = someFunc; // ok
> F2 funcPtr2 = someFunc; // error
> //-------------------------------------------

Yep. Here `funcPtr2` is not a pointer, it's a function. And one cannot
initialize a function.


> Likewise:
>
> //-------------------------------------------
> std::function<int(int)> funcObj1; // ok
> std::function<int(*)(int)> funcObj2; // error
>
> std::set<int, bool(*)(int, int)> set1; // ok
> std::set<int, bool(int, int)> set2; // error
> //-------------------------------------------
>
> So they are like the reverse of each other.
>
> F2 above can be used for a function declaration, which might give some
> insight into what it means. In other words:
>
> //-------------------------------------------
> F2 someFunction; // ok
>
> int main()
> {
> int value = someFunction(5); // ok.
> }
>
> int someFunction(int v) { return v+1; }
> //-------------------------------------------

Note that this use of a function type F2 adds a third quirk: that in a
declaration of a non-static member function the type changes to, not
pointer, but a member function type distinct from F2.

Example:

-------------------------------------------------------------------------
#include <iostream>
using namespace std;

using F = void(int);

struct Gah
{
int data;
F func;
};

void Gah::func( int x ) { cout << x << endl; }

auto main()
-> int
{
Gah().func( 666 );

#ifdef SURPRISE
Gah g = {};
int* pd = &g.data; // OK.
F* pf = &g.func; //! No, not same type.
(void) pd; (void) pf;
#endif
}
-------------------------------------------------------------------------

Visual C++ 2019 reasonably just protests about the syntax. After all
it's invalid syntax.

g++ 9.2 unreasonably accepts the syntax, but then reacts to the obvious
meaning and says


error: cannot convert 'void (Gah::*)(int)' to 'void (*)(int)' in
initialization


- Alf

Ben Bacarisse

unread,
May 31, 2020, 9:50:15 PM5/31/20
to
"Alf P. Steinbach" <alf.p.stein...@gmail.com> writes:

> Functions and function pointers are often interchangeable, just as
> arrays and array item pointers are often interchangeable, because
>
> * they have implicit conversion to pointer where a pointer is needed,
> called a type DECAY, and
> * the main functionality is only defined for pointers.
>
> This is understandable given the history of C [...]

> For example, there is no such thing as indexing of an array: formally
> indexing is defined for pointers only.
>
> And for example, there is no such thing as a call of a function: the
> call syntax is available for pointers only.
>
> Indexing and calling works for actual arrays and functions only
> because of the implicit conversions to pointer.

This not how things happened historically. Originally, functions were
called as functions, and a call through a pointer-to-function required a
dereference:

(*f)(... args ...)

The switch to calling through a pointer (and the associated ubiquity of
the decay of functions to pointers) came along only in C90.

As you say, this change makes arrays and functions analogous, but it was
not there in the early days.

--
Ben.

Alf P. Steinbach

unread,
Jun 1, 2020, 1:08:26 AM6/1/20
to
Oh look, a draft of C89.
<url: http://port70.net/~nsz/c/c89/c89-draft.html#3.3.2.2>

[quote]
§3.3.2.2 (Function calls)
The expression that denotes the called function shall have type pointer
to function returning void or returning an object type other than array.

§3.2.2.1/4 (Lvalues and function designators)
A function designator is an expression that has function type. Except
when it is the operand of the sizeof operator25 or the unary & operator,
a function designator with type ``function returning type '' is
converted to an expression that has type ``pointer to function returning
type .''
[/quote]

But even though that's incompatible with your claim, and compatible with
how I remembered things (I learned C from K&R), you could be generally
right just with the details botched a little: that this was introduced
in C89 instead of C90, and that it was not so in K&R.

- - -

I doubted that but then I thought I'd check before posting.

And as it turns out I didn't have to drive a couple of kilometers and
dive down into boxes of books to find my old copy of K&R, because the
Internet Archive has a copy, at <url:
https://www.andreafortuna.org/2016/05/26/first-edition-of-the-c-programming-language-freely-available-on-internet-archive/>.

And you're right except for the detail of when: K&R first edition states
in section 7.1, page 186, that a function call must be an expression
directly of function type (K&R doesn't include the arguments list in the
call), and that a function name is converted to pointer except when used
as a call. Oh well. How could I have forgotten that weird idiosyncrasy.

Thanks for the correction.

- Alf (now a perpetrator of "false history" :-o )

James Kuyper

unread,
Jun 1, 2020, 10:13:33 AM6/1/20
to
On 6/1/20 1:08 AM, Alf P. Steinbach wrote:
> On 01.06.2020 03:50, Ben Bacarisse wrote:
...
>> This not how things happened historically. Originally, functions were
>> called as functions, and a call through a pointer-to-function required a
>> dereference:
>>
>> (*f)(... args ...)
>>
>> The switch to calling through a pointer (and the associated ubiquity of
>> the decay of functions to pointers) came along only in C90.
>>
>> As you say, this change makes arrays and functions analogous, but it was
>> not there in the early days.
>>
>
> Oh look, a draft of C89.
> <url: http://port70.net/~nsz/c/c89/c89-draft.html#3.3.2.2>
>
> [quote]
> §3.3.2.2 (Function calls)
> The expression that denotes the called function shall have type pointer
> to function returning void or returning an object type other than array.
>
> §3.2.2.1/4 (Lvalues and function designators)
> A function designator is an expression that has function type. Except
> when it is the operand of the sizeof operator25 or the unary & operator,
> a function designator with type ``function returning type '' is
> converted to an expression that has type ``pointer to function returning
> type .''
> [/quote]

...
> But even though that's incompatible with your claim, and compatible with

How is that incompatible? The ANSI C89 standard and the ISO C90 standard
are essentially the same (the only significant difference was that 3
sections were added the beginning to meet ISO requirements for the
format of standard documents, resulting in every top level section
number increasing by 3, requiring a change to every single
cross-reference). He said that the change was made in C90, so that
supports his claim.

> how I remembered things (I learned C from K&R), you could be generally
> right just with the details botched a little: that this was introduced
> in C89 instead of C90, and that it was not so in K&R.
>
> - - -
>
> I doubted that but then I thought I'd check before posting.
>
> And as it turns out I didn't have to drive a couple of kilometers and
> dive down into boxes of books to find my old copy of K&R, because the
> Internet Archive has a copy, at <url:
> https://www.andreafortuna.org/2016/05/26/first-edition-of-the-c-programming-language-freely-available-on-internet-archive/>.
>
> And you're right except for the detail of when: K&R first edition states
> in section 7.1, page 186, that a function call must be an expression
> directly of function type (K&R doesn't include the arguments list in the
> call), and that a function name is converted to pointer except when used
> as a call. Oh well. How could I have forgotten that weird idiosyncrasy.

Easily enough - the rules were carefully written for backwards
compatibility: in the expression (*function_pointer)(arguments) the
sub-expression *function_pointer still has a function type, as was
required in K&R C, but in C89(==C90) it gets implicitly converted back
into a function pointer, as required in C89. As a result, the main
practical consequence was that in C89 and later, you no longer needed to
dereference a function pointer in order to call the function, code that
would have been an error in K&R C.

Juha Nieminen

unread,
Jun 2, 2020, 12:59:25 AM6/2/20
to
Ben Bacarisse <ben.u...@bsb.me.uk> wrote:
>> std::set<int, bool(*)(int, int)> set1; // ok
>> std::set<int, bool(int, int)> set2; // error
>> //-------------------------------------------
>>
>> So they are like the reverse of each other.
>
> A set hold data objects, and pointers are data objects so a set of
> pointers makes sense. Functions are not considered to be data objects
> in either C or C++ so you can't put them into collections.

The second template parameter is a comparator, not the element type.

Ben Bacarisse

unread,
Jun 2, 2020, 5:37:03 AM6/2/20
to
Ah, yes, I missed the comma. Oddly, though, the reason you can't use an
actual function type there is the same. A compare "thing" must be a
copy-constructable object, so a function type is ruled out. The
comparator is put into the collection -- it's part of the collection's
data.

--
Ben.
0 new messages