I try not to commit the sin of conveying unimportant information, but one of the hazards of spending decades in this business is that you learn a lot. After a while, it can be hard to evaluate what's worth knowing and what's best left unsaid. With that in mind, the current draft of Effective Modern C++ contains this passage (more or less):
Suppose our function f can have its behavior customized by passing it a function that does some of its work. Assuming this function takes and returns ints, f could be declared like this:
void f(int (*pf)(int)); // pf = "processing function"
It’s worth noting that f could also be declared using a simpler non-pointer syntax. Such a declaration would look like this, though it’d have the same meaning as the declaration above:
void f(int pf(int)); // declares same f as above
One of my reviewers argues that the second way of declaring a function pointer parameter is not only not worth noting, it's not worth knowing. That means I should omit it. But I find myself reluctant to do that. I like the fact that function pointer parameters can be declared without throwing in additional parentheses and figuring out which side of the asterisk the parameter name has to go on. But maybe that's just me.
So what do you think? Should I jettison the aside about the asterisk-free way to declare the parameter pf, or should I keep it?
Thanks,
Scott
Since this makes code easier to read, I think it is worth knowing about.
ReplyDeleteIt's that simple.
I'd put the version with the asterisk character into a footnote, mentioning this was the only form available in "the dark ages" before the pointer-free version became part of a C or C++ standard.
ReplyDeleteMaybe better use std::function in this cases?
ReplyDeleteI agree with Keith. The latter is simpler and cleaner and less likely to cause typos in the parentheses or asterisk, so should be preferred.
ReplyDeleteIsn't the 2nd form what makes the most vexing parse possible (not in this case, but in general)?
ReplyDeleteThe second way is more clean to read. Make it simple imho.
ReplyDeleteIt's a valid part of the language. At least mentioning it is important. It's definitely more common to use parentheses and and an asterisk, and it is valuable to also mention this.
ReplyDeleteFor example: sometimes I accidentally declare function pointers when I mean to call constructors; this information is helpful to understand specific compiler errors.
This comment has been removed by the author.
ReplyDeleteMy initial reaction was basically OMG WHY DID NOBODY EVER TELL ME THIS? However, I'm not sure it actually matters to a general C++ audience.
ReplyDeleteAs I assume you go on to say after that excerpt, first-class function parameters should almost always be generic function objects in modern C++ (either a template type, or type-erased with std::function) rather than function pointers. That being the case, your readers probably don't need to know how to write function pointer parameters. They may sometimes need to know how to read them, in order to deal with legacy code, but given the seeming rarity of that syntax, it's probably not worth teaching for those purposes either.
One thing that would have saved me some mental anguish in the past is this simple fact:
ReplyDeleteThe pointer-free syntax informs the common syntax for function types in template definitions, such as:
std::function<int(int)> pf;
Leaving out this nugget makes explaining templated declarations like this more confusing.
I prefer the pointer syntax in that case since the two syntaxes aren't interchangeable in general and the pointer syntax I believe avoids some confusion.
ReplyDeleteFor example, suppose I declare types like this:
using T1 = void (*)();
using T2 = void ();
and I have a function void f();
Then I can write T1 x = f, but not T2 x = f.
As someone who learned C (and C++) a long time ago, and uses them daily, I literally didn't know about the 2nd form, which I think is better, until your post.
ReplyDeleteSo, chalk up another vote for "keep it in."
I think it could at least be mentioned, so as to point out:
ReplyDeletestruct bar {};
struct foo { foo(bar) {} };
int main()
{
// This is a function declaration
foo foobar( bar() );
// This is an object
foo foobar( (bar()) );
}
@grey wolf, the type passed to std::function in your example isn't a function pointer type, you are passing a function type. The type-decay from function to function pointer just occurs in function parameter declarations.
ReplyDeleteI agree with the reviewer and would further say that the automatic 'adjustments' to argument types, as the C++ spec calls them, are an abomination and they should not be used. This is definitely one of those things inherited for C compatibility that makes C++ worse.
ReplyDeletegrey wolf said...
"The pointer-free syntax informs the common syntax for function types in template definitions, such as:
std::function pf;"
No, in fact the template syntax is doing what it should do precisely because does not involve the the 'adjustments' that are specified for function parameters. Knowing about the adjustments should have no bearing on knowing how to write a function's type.
auto f(auto pf(int) -> int) -> void;
ReplyDelete:P
My vote is to be complete and leave it in. It is much better since it will answer the question that will come up as a result of omitting it.
ReplyDeleteAre function references commonly used? If so, the explicit pointer (with the asterisk) is preferable to me.
ReplyDeleteI never knew!
ReplyDeleteLeave it in, I was happy to learn this.
Saying that's not worth knowing is exactly like saying "f(int a[10])" is not worth knowing or "f(const int n)" is not worth knowing.
ReplyDeleteWell, I've been doing C++ for a couple decades and didn't know about this one. I guess I should feel bad about that but I've never seen it used either. I think it's clearer, so its lack of popularity may simply be an example of a widespread ignorance.
ReplyDeleteSo yeah, I'd say it's worth mentioning.
Thanks to everybody for their comments. I've decided to keep the comment in the draft book.
ReplyDeleteFWIW, many of the other issues that have been raised here (e.g., the most vexing parse, function pointer types vs. function types during template type deduction,specifying function types for use with std::function, etc.) are discussed elsewhere in the book.
This is the first time I ever saw the non-pointer syntax (I've been on a detour through Java for a few years), so I find it worthwhile to see both.
ReplyDeleteIn either case, I think it's nicer to have a 'using' beforehand and avoid the C gibberish, that way the name of the parameter isn't buried in a forest of parentheses
ReplyDeleteusing F = int(*)(int);
F f = ::abs; // old C abs
Another vote in the "I've used C++ for ages and I sure wish I'd known about that syntax earlier!" bin. That's nifty, keep it! :-)
ReplyDeleteDon't suggest `std::function<>` (it's shooting a fly with a canon).
ReplyDeleteJust use
template <typename Sig> using fptr = Sig*;
Simple and succinct. It's also the kind of thing to use to defuse array-ref parameters/return types.
Non-pointer syntax is worth knowing, but the sad thing is that there is no analogue for member functions (at least I was unable to find the correct syntax, is there any?).
ReplyDelete@cola: As far as I know, there is only one syntax for expressing member function pointers, though of course you can create a typedef or type alias.
ReplyDeleteThe second syntax seemed cool at first glance. But it doesn't work if the return type is a pointer :( This means that you have to think what syntax to use depending on the return type, and that's a reason enough not to use or mention it.
ReplyDeleteI think its worth mentioning... plus the book won't get any more expensive now since I've already paid for it.
ReplyDelete