14

I'm trying to register a function that returns an int to be called at the end of a program using the atexit() function. (Specifically, the endwin() function from ncurses.)

But since atexit() needs a pointer to a void function, I ran into a problem. I tried the following:

static_cast<void (*)()>(endwin)

but static_casting from an int function to a void function doesn't seem to be allowed.

Is what I'm trying to accomplish possible at all, and if yes, how?

Note: I'm willing to just ignore the return value of the function.


Edit: I also tried creating a lambda function, which seems to do what I want:

atexit([]{ endwin(); });

Is this a good solution compared to a wrapper/forwarding function? (Other than that it needs C++11 and avoids defining a new function whose sole purpose is just forwarding another function.)

Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • 4
    Did you try a `reinterpret_cast` ? – Basile Starynkevitch Feb 14 '15 at 14:17
  • 4
    @BasileStarynkevitch: note that calling a function through a pointer of incorrect type results in undefined behavior according to 5.2.10 [expr.reinterpret.cast] paragraph 6: "The effect of calling a function through a pointer to a function type (8.3.5) that is not the same as the type used in the definition of the function is undefined." – Dietmar Kühl Feb 14 '15 at 14:23
  • Yes, it is undefined, but I guess that on most Linux x86-64 implementations that won't matter. Of course, some ABI may specify different calling conventions (depending on the function signature), and in that case, chaos would happen – Basile Starynkevitch Feb 14 '15 at 14:24
  • @BasileStarynkevitch That seems to work for me, but as Dietmar Kühl mentioned, it doesn't seem to be guaranteed to work. – Emil Laine Feb 14 '15 at 14:29
  • 4
    If `endwin` is returning a complex data type, such as `std::string` or `struct X`, then it will return the data to a space given by some hidden argument. If the data type returned is "simple" (e.g. `int`, `char` or some pointer), the return value is passed in a register, and that's usually harmless. But Dietmar's solution is the "works all the time, every time" [and the compiler will probably optimis it so that it's very little overhead if anything in the case of "return value is in a register and therefore can be ignored] – Mats Petersson Feb 14 '15 at 14:38
  • The lambda is identical to a wrapper/forwarding function. it's just an anonymous wrapper/forwarding function. – Raymond Chen Feb 14 '15 at 15:18

3 Answers3

23

Function pointers can't be converted. Just register a forwarding function:

#ifdef __cplusplus
extern "C"
#endif
void endwin_wrapper() { endwin(); }
...
atexit(endwin_wrapper);

Since you tagged your question C++: if you define the forwarding function in C++ you need to declare it to be extern "C" to have the proper language linkage.

Dietmar Kühl
  • 150,225
  • 13
  • 225
  • 380
  • 1
    Do you really need `extern "C"`? The name mangling doesn't matter a bit, the function pointer types of C and C++ are completely compatible, I thought. Unless there's some super-pedantic language-lawyer issue here...? – Nicholas Wilson Feb 14 '15 at 15:17
  • 2
    @NicholasWilson: See: http://stackoverflow.com/questions/2594178/why-do-you-need-extern-c-for-c-callbacks-to-c-functions – Dietrich Epp Feb 14 '15 at 15:19
  • 11
    `atexit()` in C++ has two overloads, one of which is `extern "C++" int atexit(void (*f)(void)) noexcept;` which takes a C++ linkage function pointer. (The other is `extern "C" int atexit(void (*f)(void)) noexcept;`, taking a C linkage function pointer.) – T.C. Feb 14 '15 at 15:55
  • What @T.C. said, but please note that if you inspect the standard library of any implementation that, contrary to the standard's requirements, does not distinguish between C and C++ linkage function types, you won't find the two overloads for `atexit` (and several other functions), since those implementations would fail to detect them as overloads. –  Feb 14 '15 at 18:43
12

With the current C++ standard, a lambda is probably the best solution to this:

atexit([]{ endwin(); });

Now there's no need to define a whole new named function with the sole purpose of just forwarding another function.


And if you one day decide you need to have more functions called at program exit, you can either define them with a new call to atexit():

atexit(anotherCleanupFunction);

or, you can just add them to the lambda body:

atexit([]{
  anotherCleanupFunction();
  endwin();
});

However, when you have multiple functions to be registered with atexit(), then a separate wrapper function to hold all those functions may be a better solution. There's no one right answer though, just write good and clear code. (A call to atexit() with by a lambda full of function calls can look rather messy.)

Community
  • 1
  • 1
Emil Laine
  • 41,598
  • 9
  • 101
  • 157
  • You may simply refer C++11 as _the current standard_. Otherwise,- as mentioned -, I like the solution. – πάντα ῥεῖ Feb 14 '15 at 15:47
  • True that would probably be better. – Emil Laine Feb 14 '15 at 16:10
  • 1
    C++11 is not the current standard, C++14 is. In C++14, a lambda is still probably the best solution, so your answer still looks fine. –  Feb 14 '15 at 18:45
  • Best in what sense? I think the wrapper solution is best, simply because it is perfectly clear what the author is doing, especially if a comment like "Needed to convert int return to void" is added. There are people who write C & C++ code with the intention of getting a job done, not seeing how many obscure features they can include in the code. – jamesqf Feb 14 '15 at 18:48
  • @jamesqf Best in the sense that you don't need to define a whole new function just to be able to register that one non-void function with atexit(). But you do have a point, I would probably take the wrapper function route as well if I had multiple functions to register with atexit(). – Emil Laine Feb 14 '15 at 19:07
  • @zenith: Also from following the 'lambda' link above, and doing a quick bit of Googling, it seems that the lambda function only works in C++ (and perhaps as an extension in gcc?). If so, it's not going to much use in C code, as the OP had the question tagged. (Of course I could be wrong, as I'd never heard of lambda functions until I read this.) – jamesqf Feb 15 '15 at 05:41
  • @jamesqf Yeah you're right, lambdas were introduced in C++11. That's why I stated that this answer is specifically for C++ in case that C users come here looking for answers. Btw I'm the OP and I tagged this C because the question is also applicable to C. And from the `static_cast` in the question one can deduce that I'm doing C++ myself. – Emil Laine Feb 15 '15 at 09:41
6

"Is what I'm trying to accomplish possible at all, and if yes, how?"

You just write your own wrapper function for endwin() and register that one

void myEndwin() {
    endwin();
}
πάντα ῥεῖ
  • 1
  • 13
  • 116
  • 190