Unfortunately, that code is not thread safe.
There is a simple rule: if a variable is accessed by multiple threads, and at least one could be writing to it, then your code is broken if you are not using thread safe types (e.g. std::atomic) or synchronisation (e.g. mutexes). Not doing so is called a race condition, and attempting to run code with a race condition is undefined.
Unfortunately, the reverse is not true, merely using synchronisation or thread safe locks does not mean your code is not broken. To write thread safe code, you have to clearly define data that is shared and take the appropriate measures to ensure that the data can be updated safely, and ensure that each thread can make progress eventually (e.g. preventing starvation and deadlocks).
Let us consider some scenarios that could arise with your code.
In the first, imagine the server thread has a single space left, and accepts a new client. This spawns a client thread. There is no guarantee that the client thread will run yet, so it is possible that another client could be accepted, because "num clients" has not been updated. What happens? Well, in this case, the server thread's loop to find a new client index won't find one. However, the value of "temp_id" has not been updated, it will still retain the value of the previous client. So now another thread will be spawned with the same "client" reference. Clearly not intended!
So that was a simple case, and we haven't even run into any of the "fun" problems yet!
On a system with multiple processors, each processor has several levels of memory caches. To be efficient, the values from memory in those caches are assumed to be owned by that processor, that is, only certain instructions will cause the cache to be evicted or bypassed to check if the value in main memory has been updated. Also, writes to memory may stay in various buffers in that processor too. The end result of this there is no guarantee when or if an update to a variable in memory may become visible to another, unless one of the special instructions mentioned is executed. So, on some architectures and some machines, your client thread's changes may not been seen by the server thread (or other client threads) for some time, or ever!
Another factor is an optimising compiler. For instance, a common optimisation is to avoid reading and writing to main memory in a loop, but rather to read the value into a register, run the loop, and write any changes after the loop is finished. Compiler writers assume that code is single threaded by default, so unless you use a special type like std::atomic, or a thread synchronisation function, the optimiser might also cause changes to go unpublished to other threads.
In some cases, a changed variable value can actually be only partially published, one more more bytes might appear to have the new value, but the others have the old value. The resulting combination is meaningless, for instance, incrementing 255 to 256 might appear as 511, a value which might be totally out of range!
So, essentially you have to think of race conditions as similar to uninitialised variables, the consequences can be difficult to predict and will often vary from run to run. This makes it very difficult to tell if you've actually fixed them, simply by adding some locking (but not enough), you can get the expected result more often due to the change in timings that creates.
The only way to do multi-threaded programming is to think of a design, and carefully implement it.
Learning how to use select() properly is far simpler, and as the others have mentioned will actually scale better than trying to use a thread per client. More importantly, you'll be able to reason about your program. You have to learn to reason in a multi-threaded way.
Some of the oldest and most mysterious, unreproducible and unsolved bugs in the system I work on are presumed to be subtle race conditions - and that program has on the order of a million lines of code for them to hide in. We have fixed a number of bugs which were race conditions, they are always tricky to find. This system has a team of experience professionals developing and maintaining it, and still doing threading correctly is Hard (with a capital H).