Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Subjunctive
Sep 12, 2006

✨sparkle and shine✨

With a background thread they might not even need to use a future; just do the blocking op and wait, that’s a big part of what threads are for!

Adbot
ADBOT LOVES YOU

Xerophyte
Mar 17, 2008

This space intentionally left blank

Rocko Bonaparte posted:

I think the main motivation for a promise is to have something run after something else finishes and has a value to spit out, but I have never used them in C++. It's kind of strange here! I figure you'd pass a function to the promise and "chain" it.

A promise-future pair are just the two ends of a communication channel that you may then pass around your code. One piece of code can write a value to the promise, which a completely unrelated piece of code somewhere else may then retrieve by reading from the future.

If you just want to poll the status of the future then std::future::wait_for(0s) != std::future_status::timeout and similar works. std::future::is_ready() is in the works (and exists in std::experimental). It has been in the works for a decade; not entirely sure what's going on with it.

std::future::wait_for(0s) may still technically block, but I believe it's lock-free in the major implementations if atomic_flag is lock free.

Xerophyte fucked around with this message at 01:23 on Oct 5, 2023

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
In Java you have a ListenableFuture that allows you to get a callback on a particular executor when the value becomes available, which means you're not burning an entire os thread for every single future you're waiting on.

It looks like C++ was considering standardising a future::then() method which would allow much the same thing, but it doesn't seem to have happened for some reason? Maybe they figured that coroutines were a better way to accomplish that goal?

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
Yeah I'm more looking for the "here's a chunk of code to run when the future's ready" version.

Currently we can end up with dozens of threads all blocked. Possibly unbounded, actually, I'd have to double-check. And there's certainly other ways to stop doing that, I'm hoping this one is the least painful :)

Thanks!

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Is this more of an async/await situation than a parallelism situation? Like you have a bunch of small computations to do before and after blocking operations and you want something to schedule in a smart way?

https://en.cppreference.com/w/cpp/thread/async looks to be pretty recently added, though…

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!
It's not really clear what you're asking for - you want something to happen after something else completes, but on what thread?

If you want it to happen on a specific thread, that waits until that specific thing is done, you want a std::future/promise.

If you want it to happen on a specific thread or pool of threads, that does various pieces of work after various events, you want a dispatcher sort of pattern, where the thing that completes the work pushes a lambda (or some other sort of object indicating the work to be done) onto a queue that wakes up a thread to consume from the queue if one is blocked, or if all the threads are busy then the action will wait 'til one of those threads is done with what it's already doing.

If you want it to do the action on the same thread as the one that was doing the thing you were waiting for to complete, you probably just want to have that thread call a callback when it's done.

I don't think there's a standard library for dispatcher-queue, but it's pretty simple, you guard a deque with a mutex and a condition variable, lock-pushback-signal-release to add work, lock-popfront-release to consume work, if there's nothing to popfront you wait for the condition variable and try again, plus you have to figure out your own termination condition and termination behavior.

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
std::future is missing then(), which is the thing that actually makes futures interesting and useful and it's kinda pointless as a result. It's relatively easy to roll your own future type which actually is useful.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
Yep more async/await.

It's a pile of C++ that an Android and an iOS app both call into. That pile of C++ in turn can call back out to the platform for database and network operations. A call sequence might be
code:
app.onClickLogIn()
    pile.logIn(user, pass)
        app.doHttp("POST", "/login", params)
            urlSession.fetch(…)
        app.saveUser(user)
            db.transaction(…)
and it's all synchronous. Even though the URL session is async (we stick a semaphore around the platform calls to make it synchronous), the database is async (ditto), and we've gotta get off the ui thread in the first place.

I'd like to change the URL session, database, and topmost (in this case, logIn()) calls to return futures/be coroutines/something. The URL session or database will complete the future on some arbitrary thread and that's ok with me, they've got their own dispatchers and thread pools, I just don't want to park a thread waiting for them to finish.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.

Jabor posted:

In Java you have a ListenableFuture that allows you to get a callback on a particular executor when the value becomes available, which means you're not burning an entire os thread for every single future you're waiting on.

It looks like C++ was considering standardising a future::then() method which would allow much the same thing, but it doesn't seem to have happened for some reason? Maybe they figured that coroutines were a better way to accomplish that goal?

Plorkyeran posted:

std::future is missing then(), which is the thing that actually makes futures interesting and useful and it's kinda pointless as a result. It's relatively easy to roll your own future type which actually is useful.

Woops, missed these. "Where is std::future::then()" is the concise way to ask what I'm going for. Seems like coroutines are the closest?

The platform barrier is handled by Djinni and it has a future that works at that barrier, but I didn't necessarily want to spread Djinni stuff all throughout the C++ code. It seems like a fairly generic future type with a then() though, so maybe it's the right thing to use.

Rocko Bonaparte
Mar 12, 2002

Every day is Friday!
Yeah I was thinking of the JavaScript version, specifically:

https://promisesaplus.com/

I think I have seen this acknowledged and used elsewhere. Python's Future class is a future, but let's you attach a callback to run something when the value is filled. I was surprised C++ was cooking something else.

roomforthetuna
Mar 22, 2005

I don't need to know anything about virii! My CUSTOM PROGRAM keeps me protected! It's not like they'll try to come in through the Internet or something!

Rocko Bonaparte posted:

Yeah I was thinking of the JavaScript version, specifically:

https://promisesaplus.com/

I think I have seen this acknowledged and used elsewhere. Python's Future class is a future, but let's you attach a callback to run something when the value is filled. I was surprised C++ was cooking something else.
I found this absence of 'then' surprising too when I first encountered C++ promises/futures, but now that I'm comfortable with them I don't understand what it is that you'd expect a future-with-then to do - it really has to do one of:

1. assign the 'then' action to a thread that is waiting for it (that's what it effectively does already)
2. do the 'then' action on the thread that's completing the future (this is just a callback, you don't need a future/promise at all for this, just pass in a std::function and name it "then")
3. post the 'then' action to a threadpool or something (which requires a whole dispatcher setup that doesn't by default exist)
4. start a whole new thread just to handle the 'then' action.

1 is what it already is, 2 doesn't need a future at all, 3 subtly requires a bunch of prerequisites, and 4 is a performance disaster.

I guess the thing that we're familiar with from Javascript-style 'then' is that there's just one thread and at some point it's in a "not doing anything" state, and that's when any async-resolved 'then' actions get resolved. But C++ doesn't have a "not doing anything" state by default, so there's no common place to resolve these things.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
The whole point of an abstraction is to abstract things, saying "you don't need the abstraction to handle this scenario because you can just reach in and do it manually" makes you wonder why anyone would bother with the abstraction you've defined instead of picking one that actually works.

If you have 2, you can implement 3 on top of it by having the code that's executed directly on the completing thread just post the actual continuation to whatever dispatcher you happen to be using, so while it would be nice to solve 3 it's not really a requirement for the thing to be useful.

Jabor fucked around with this message at 05:31 on Oct 5, 2023

Nalin
Sep 29, 2007

Hair Elf

roomforthetuna posted:

I found this absence of 'then' surprising too when I first encountered C++ promises/futures, but now that I'm comfortable with them I don't understand what it is that you'd expect a future-with-then to do - it really has to do one of:

1. assign the 'then' action to a thread that is waiting for it (that's what it effectively does already)
2. do the 'then' action on the thread that's completing the future (this is just a callback, you don't need a future/promise at all for this, just pass in a std::function and name it "then")
3. post the 'then' action to a threadpool or something (which requires a whole dispatcher setup that doesn't by default exist)
4. start a whole new thread just to handle the 'then' action.

1 is what it already is, 2 doesn't need a future at all, 3 subtly requires a bunch of prerequisites, and 4 is a performance disaster.

I guess the thing that we're familiar with from Javascript-style 'then' is that there's just one thread and at some point it's in a "not doing anything" state, and that's when any async-resolved 'then' actions get resolved. But C++ doesn't have a "not doing anything" state by default, so there's no common place to resolve these things.

I think its just people getting confused at how other languages handle this stuff. What they actually want is a packaged way to throw a chunk of code that sets a promise into its own thread that, once it completes, then executes a callback in that same thread.

In JS you'll have async APIs that return a Promise that you can then bind a callback to with .then(). The goal is to throw that code off into its own thread and forget about it. The .then() is supplied to take the results of that Promise and choose how to handle them at the time of completion. You don't care about when all of this happens.

There is nothing built into the STL to handle this, but you could design your own system to do this using std::promise and std::future.

Nalin fucked around with this message at 05:39 on Oct 5, 2023

Absurd Alhazred
Mar 27, 2010

by Athanatos
If you were in (sufficiently updated) Windows Land you could use Microsoft's Concurrency Runtime, which includes a template library with then support.

pokeyman
Nov 26, 2006

That elephant ate my entire platoon.
then() also returns another future for the result of running the closure passed to then(), so it lets you get away from increasingly indented callbacks. I agree it doesn't look super useful with a single call.

And in my case, I have a future type with then() that translates between C++ and Java/Objective-C, so I figure if I can use something similar within C++ it'll be overall easier to follow. (I can't add similarly translatable callbacks without a ton of work.)

nielsm
Jun 1, 2009



roomforthetuna posted:

2. do the 'then' action on the thread that's completing the future (this is just a callback, you don't need a future/promise at all for this, just pass in a std::function and name it "then")

The other issue with this is, what if the promise has already been filled when you call 'then'? Which thread does the 'then' then resolve on? It can't be the thread that filled the promise, it has probably already moved on to some other work, or maybe simply exited. It can't be the thread calling 'then' on the future because then you might suddenly be blocking in places you don't want to block.
The 'then' really has to be supplied at promise-future pair creation time to be able to guarantee it running on the promise-filling thread.


And I just want to note that I personally found std::promise/future a very convenient way to express waiting for a thread to produce a result and then consume it, without needing to all the logic with lower level primitives.

Jabor
Jul 16, 2010

#1 Loser at SpaceChem
It just happens in the thread calling then(). Why would you be blocking if the promise has already been filled? If it's been filled then by definition the value is already available.

You do need to write the code inside the callback under the presumption that it could be called in either place though.

go play outside Skyler
Nov 7, 2005


In charge of putting ci/cd for a bunch of C/C++ mixed projects. Anyone have any experience with linters?

I really want to do something like force every public function to have doxygen style comments, and enforce some level of style but that can be adapted per-project.

Beef
Jul 26, 2004
Caveat Emptor: std::async basically spawns a new pthread in every stdlib implementation I worked with. We sped up a microservice platform by several orders of magnitude because 80% of the CPU time was spent on std::async's loving pthread_create. It was a good eye opener as to why most C++ shops seem to use or roll their own thread pool lib.

Sweeper
Nov 29, 2007
The Joe Buck of Posting
Dinosaur Gum

go play outside Skyler posted:

In charge of putting ci/cd for a bunch of C/C++ mixed projects. Anyone have any experience with linters?

I really want to do something like force every public function to have doxygen style comments, and enforce some level of style but that can be adapted per-project.

I feel like a style check would be the absolute last thing on my list, c/c++ users are super finicky about that poo poo ime and will fight it tooth and nail

I think if you can get a build+test+artifact store happy per target and make it easy to run compiler upgrade tests, platform changes, etc that is amazing and much better than the crap I regularly deal with

Xarn
Jun 26, 2015

Beef posted:

Caveat Emptor: std::async basically spawns a new pthread in every stdlib implementation I worked with. We sped up a microservice platform by several orders of magnitude because 80% of the CPU time was spent on std::async's loving pthread_create. It was a good eye opener as to why most C++ shops seem to use or roll their own thread pool lib.

It is literally standardized to do that. Only MSVC's std::async is useful and that's because they break standard there. :v:

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...
std::execution aka "senders and receivers" looks neat, but I’ll probably be in retirement before it's released :corsair:

https://www.youtube.com/watch?v=hLbhNTRKafo

Xerophyte
Mar 17, 2008

This space intentionally left blank

nielsm posted:

And I just want to note that I personally found std::promise/future a very convenient way to express waiting for a thread to produce a result and then consume it, without needing to all the logic with lower level primitives.

Likewise. My thread worker pool has a business end of
C++ code:
template<class Function, class... Args >
std::future<...> threadpool::queue_task(Function&& f, Args&&... args);
Internally I use std::packaged_task for the task state. It's only ~100 lines, easy to use, and has worked fine for years now (minus one MSVC bug related to move-only functors).

I can see how, for promises, having a lambda you run on promise::set_value would be useful, but it seems quite easy to implement in a custom promise/future pair that wraps the std ones.

b0lt
Apr 29, 2005

Xarn posted:

It is literally standardized to do that. Only MSVC's std::async is useful and that's because they break standard there. :v:

std::launch::async is allowed to be implemented however the implementation wants, as long as it doesn't run it on the same thread

nielsm
Jun 1, 2009



I'm going to implement it by taking out an ad on a Times Square screen with a QR code with a link to the async code recompiled to js, and whenever someone hopefully completes it they upload the result somewhere I can poll.
No wait, that will make shared memory and such really annoying. Never mind.

Computer viking
May 30, 2011
Now with less breakage.

nielsm posted:

I'm going to implement it by taking out an ad on a Times Square screen with a QR code with a link to the async code recompiled to js, and whenever someone hopefully completes it they upload the result somewhere I can poll.
No wait, that will make shared memory and such really annoying. Never mind.

What, no messenger pigeons?

nielsm
Jun 1, 2009



The pigeons can be trained to direct passerbys' attention to the ad.

Xarn
Jun 26, 2015

b0lt posted:

std::launch::async is allowed to be implemented however the implementation wants, as long as it doesn't run it on the same thread

:wrong:

quote:

If launch​::​async is set in policy, calls invoke(auto(std​::​forward<F>(f)), auto(std​::​forward<Args>(args))...) ([func.invoke], [thread.thread.constr]) as if in a new thread of execution represented by a thread object with the values produced by auto being materialized ([conv.rval]) in the thread that called async.

The thread has to be "fresh" e.g. in regards to thread local state. An implementation could in theory do a ton of work to clean up existing threads in a thread pool, but this is a significant limitation on what the implementation can do

nielsm
Jun 1, 2009



Is that only your own reading of "as if in a new thread of execution", or has that been confirmed/expanded upon by someone involved in writing that text?

Are thread-local variables in new threads defined to be zeroed memory, or undefined value?
Edit: And regardless of the answer to that (well I looked it up, they are initialized at thread startup), what is more costly? Launching a new OS thread and initializing thread local variables in it, or re-using an existing OS thread and initializing thread local variables in it? What other state does a thread have? Besides potentially not-cleaned-up ownership of OS objects, which imo is a bug in the program.

nielsm fucked around with this message at 17:11 on Oct 6, 2023

Subjunctive
Sep 12, 2006

✨sparkle and shine✨

Just statically determine if the called function reads TLS before writing, and if not (which will always be the case because it can’t assume there’s anything there!) skip that part. Optimization is just not getting caught cheating after all.

Dylan16807
May 12, 2010

Jabor posted:

It just happens in the thread calling then(). Why would you be blocking if the promise has already been filled? If it's been filled then by definition the value is already available.

You do need to write the code inside the callback under the presumption that it could be called in either place though.
You have functions X and Y, and they both take several seconds to run. You want to run some data through them on a side thread.

For whatever reason you're not ready to set up Y immediately. So your main thread sets up a promise for X and launches a thread to do X.

Then later you do ".then(Y)" in the main thread.

If X already being filled causes you to run Y on the main thread, then your entire program will freeze for the duration of Y.

Plorkyeran
Mar 22, 2007

To Escape The Shackles Of The Old Forums, We Must Reject The Tribal Negativity He Endorsed
That is in fact a scenario where then() is not appropriate. That doesn't make it not useful for other things.

Absurd Alhazred
Mar 27, 2010

by Athanatos
Yeah, I'm pretty sure these primitives are for you to set up a digraph of tasks now that either starts working immediately or that you can dispatch later, and the runtime will then manage load-balancing on them for you.

Twerk from Home
Jan 17, 2009

This avatar brought to you by the 'save our dead gay forums' foundation.
What are the downsides of statically linking libstdc++ or libgcc? I'd appreciate an authoritative source on static vs dynamic linking of these libraries in general, because I interact with tons of tools that have very questionable build processes, frequently including statically linking glibc, libgcc, and libstdc++ with -static, -static-libgcc, or -static-libstdc++. Does anyone have any textbooks and/or posts they'd recommend on this subject?

I know that statically linking glibc is a bad idea because it will still need to load the system's glibc anyway, and it's possible to get weird failures. I've had a decent track record of getting small improvement PRs accepted to these kinds of tools, and I'm wondering if I should focus my efforts only on dynamically linking glibc, or if it's worth trying to find a portable way to dynamically link libcc and libstdc++ too?

The best thoughts I've found on this are a decade old Stack Overflow discussion that links a blog post from 2005: https://stackoverflow.com/questions/13636513/linking-libstdc-statically-any-gotchas.

As much as I would like efforts to move towards shipping your dependencies as dynamic libraries and using LD_LIBRARY_PATH to use them, I feel like baby steps are the easiest place to start. Hell, I also have started seeing more and more Docker containers that just have statically linked binaries in them, but that's a slightly different thing.

Edit: Related, I've never really worked with RPATHs or been the one to set them or think about them, but that behavior mentioned where you can set RPATH to $ORIGIN to make a binary search for .sos next to it sounds like something that I'd want most of the time. I've always envied how Windows handles .dlls compared to having to use environment variables to manage finding libraries on Linux.

Twerk from Home fucked around with this message at 19:53 on Oct 27, 2023

PDP-1
Oct 12, 2004

It's a beautiful day in the neighborhood.
I'm working on a project that would benefit from having a good function call trace system, after reading up on stuff around the internet it seems like this is a format that can do the job:

code:
#define TRACE(...) do { printf(...); } while(0);
It basically lets you plop in a printf() wherever you need it while debugging, then comment it out to

code:
#define TRACE(...) // do { printf(...); } while(0);
and it compiles to nothing if you don't need it. Nice! But why is the printf() call wrapped inside of the do-while loop?

pseudorandom name
May 6, 2007

Well, you screwed up your examples, it should be do { } while (0)

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man

PDP-1 posted:

I'm working on a project that would benefit from having a good function call trace system, after reading up on stuff around the internet it seems like this is a format that can do the job:


well... I think more typically you do this:
code:
#define TRACE(...) do {printf(...);} while (0)
Note the lack of the semicolon after the while(0). This lets you use the macro in a way that makes it feel more naturally like code, e.g. TRACE("oh no!");. And the reason for the do-while is that when you comment it out (or more typically depend on a value from the build system) you don't comment out the entire definition, you do this:
code:
#define TRACE(...) do {/*printf(...);*/} while (0)
so that semicolon still has something to do, and the empty loop gets compiled out. it seems a little annoying to comment it like that, which is true, and is why it's more usually something like this
code:
#ifdef DEBUG
#define __DO_TRACE(...) printf(...);
#else
#define __DO_TRACE(...) 
#endif
#define TRACE(...) do { __DO_TRACE(...) } while (0)
or whatever you build system gives you for debug/release builds, or something like that

Dijkstracula
Mar 18, 2003

You can't spell 'vector field' without me, Professor!

I think the more salient explanation comes less from "where should the semicolon be inserted" and more from wanting your TRACE() macro to always expand to an expression statement. First, convince yourself that do { a; b; c; } while (0) is exactly the same thing as a; b; c;; the while test will fail and we'll never return to the top of the loop, so the body is executed just once.

OK, so: Consider a more generalized version of your TRACE macro: maybe we want to be more or less verbose depending on some parameter, so each macro will actually be an if-statement: will the following program compile without error or warning...?

C code:
 $ cat -n foo.c
     1	#include <stdio.h>
     2
     3	#define VERBOSITY 40
     4
     5	#define WARN(msg) if (VERBOSITY > 10)  fprintf(stderr, "WARN: " msg "\n")
     6	#define DEBUG(msg) if (VERBOSITY > 30) fprintf(stderr, "DEBUG: " msg "\n")
     7	#define TRACE(msg) if (VERBOSITY > 50) fprintf(stderr, "TRACE: " msg "\n")
     8
     9	int main() {
    10	    if (1 < 2)
    11	        TRACE("Arithmetic works");
    12	    else
    13	        WARN("Numbers are seriously broken...");
    14
    15	    return 0;
    16	}
 $
This looks reasonable until we actually try it!

code:
 $ gcc -Wall foo.c && ./a.out
foo.c:12:5: warning: add explicit braces to avoid dangling else [-Wdangling-else]
    else
    ^
1 warning generated.
WARN: Numbers are seriously broken...
 $
What the heck, what gives?? Let's expand the macro ourselves and see...

C code:
    10	    if (1 < 2)
    11	        if (VERBOSITY > 50) fprintf(stderr, "TRACE: " "Arithmetic works" "\n");
    12	    else
    13	        if (VERBOSITY > 10)  fprintf(stderr, "WARN: " "Numbers are seriously broken..." "\n");
    14
Now, it's clear: the else that was meant to match with the if on line 10 is now matching the if inside the expanded macro on line 11! So, we need whatever TRACE and WARN expand out to not destructively interfere with their surrounding context. By convention, this is done with a do while(0) construct, as you've learned, which encapsulates the body of the macro in its own scope. Now, what if the macros were changed to this construct?

C code:
  5 #define WARN(msg)  do { if (VERBOSITY > 10)  fprintf(stderr, "WARN: " msg "\n"); } while (0)
  6 #define DEBUG(msg) do { if (VERBOSITY > 30) fprintf(stderr, "DEBUG: " msg "\n"); } while (0)
  7 #define TRACE(msg) do { if (VERBOSITY > 50) fprintf(stderr, "TRACE: " msg "\n"); } while (0)
Well, if we expand it by hand, we see that interference disappears:

C code:
    10	    if (1 < 2)
    11	        do { if (VERBOSITY > 50) fprintf(stderr, "TRACE: " "Arithmetic works" "\n"); } while (0);
    12	    else
    13	        do { if (VERBOSITY > 10)  fprintf(stderr, "WARN: " "Numbers are seriously broken..." "\n"); } while (0)
--

edit: now a question for you, OP: If we need the body of the macro to be isolated in its own scope, what's the problem with simply wrapping it in its own block, like so?

C code:
    5 #define WARN(msg)  { if (VERBOSITY > 10)  fprintf(stderr, "WARN: " msg "\n"); }
    6 #define DEBUG(msg) { if (VERBOSITY > 30) fprintf(stderr, "DEBUG: " msg "\n"); }
    7 #define TRACE(msg) { if (VERBOSITY > 50) fprintf(stderr, "TRACE: " msg "\n"); }
e: vvv ty :tipshat:

Dijkstracula fucked around with this message at 03:41 on Nov 1, 2023

Phobeste
Apr 9, 2006

never, like, count out Touchdown Tom, man
That's a much better explanation, thanks!

Adbot
ADBOT LOVES YOU

Zopotantor
Feb 24, 2013

...und ist er drin dann lassen wir ihn niemals wieder raus...

Dijkstracula posted:

Iedit: now a question for you, OP: If we need the body of the macro to be isolated in its own scope, what's the problem with simply wrapping it in its own block, like so?

C code:
    5 #define WARN(msg)  { if (VERBOSITY > 10)  fprintf(stderr, "WARN: " msg "\n"); }
    6 #define DEBUG(msg) { if (VERBOSITY > 30) fprintf(stderr, "DEBUG: " msg "\n"); }
    7 #define TRACE(msg) { if (VERBOSITY > 50) fprintf(stderr, "TRACE: " msg "\n"); }

This has the same problem as including a semicolon at the end of a statement-like macro: users can leave off the semicolon when calling it. AND THEY WILL :argh:

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply